【Vue】uniapp(vue3)+node.js+websocket(实现实时通信效果)
CrazyPanda发表于:2023-12-05 19:48:47浏览:303次
概要
uniapp基于vue3,小程序的聊天功能
项目是基于node.js服务器搭建的简易双向通信网页,实现了实时更新在线人数以及用户间即时通讯的功能。
整体架构流程
后台接口代码
1、首先我们可以通过Express 应用程序生成器快速搭建一个后台框架。(这快可以参考官网)
2、服务端
/** * WebSocket模块 */ const { WebSocketServer } = require('ws'); // 端口号 const server = new WebSocketServer({ port: 8082 }); // 存储聊天信息 let chatInfo = [] // 存储在线用户 let onlineUser = [] server.on("connection", (socket, req, res) => { console.log('链接成功'); // 用户注册 // 获取当前连接websocket的用户uid let uid = req.url.substr(1) socket["id"] = uid let index = onlineUser.findIndex(val => val.id === uid) if (index == -1) { onlineUser.push(socket) } // 从客户端接收消息 socket.on("message", (data) => { // 将收到的消息转成对象格式 let msg = data.toString() let message = JSON.parse(msg) // console.log(message, '123456789'); // 通知当前用户消息发送成功 socket.send(JSON.stringify({ message: message.message, types: message.types, toid: message.fid, tip: 0 })) // 存储当前用户发送的数据 chatInfo.push({ from: uid, to: message.fid, message: message.message, types: message.types }) // console.log(onlineUser); // 根据fid匹配到接收消息的用户并发送通知 onlineUser.forEach(item => { if (item.id == message.fid) { console.log("发送消息成功"); item.send(JSON.stringify({ message: message.message, types: message.types, from: uid, tip: 1 })) } }) }); // 连接关闭后清除在线用户 socket.on('close', res => { let newUserArr = [] let newUserIds = [] for (let i = 0; i < onlineUser.length; i++) { let val = onlineUser[i] if (val.id !== uid) { newUserArr.push(val) newUserIds.push(val.id) } } onlineUser = newUserArr let User = JSON.stringify({ onlineUser: newUserIds, type: 'users' }) onlineUser.forEach((client) => client.websocket.send(User)) console.log('用户离开了'); }) socket.send(JSON.stringify({ msg: '连接已建立', type: 'system' })) });
3、用户端(好友列表)
3.1、首先在onLoad生命周期中判断是否链接后端
onLoad(() => { uni.connectSocket({ url: 'ws://localhost:8082/' + store.userid, success: data => { console.log(data); } }) uni.onSocketOpen(function(res) { socketOpen = true; for (var i = 0; i < socketMsgQueue.length; i++) { sendSocketMessage(socketMsgQueue[i]); } socketMsgQueue = []; }); uni.onSocketClose(function(res) { console.log('WebSocket 已关闭!'); }); uni.onSocketError(function(res) { console.log('WebSocket连接打开失败,请检查!'); }); receiveSocketMsg() })
3.2、获取好友列表
// 获取好友数据 const getFrineds = () => { uni.request({ url: 'http://localhost:3461/wsb/getfreind', data: { uid: store.userid, }, method: 'POST', success: (data) => { let code = data.data.code if (code == 200) { let res = data.data.query if (res.length > 0) { for (let i = 0; i < res.length; i++) { List.value.push(res[i]) } } List.value = myfun.paixu(List.value, 'lastTime', 0) // console.log(List.value); // 获取好友内信息 for (let i = 0; i < List.value.length; i++) { getLastMsg(List.value, i) getunread(List.value, i) } } else { uni.showToast({ title: "服务器出错啦!", icon: "none", duration: 2000 }) } } }) }
3.3、好友列表实时通信
// socket聊天数据接收 const receiveSocketMsg = () => { uni.onSocketMessage(function(res) { console.log('收到服务器内容123:' + res.data); const msggs = JSON.parse(res.data) let nmsg = '' if (msggs.types == 0) { nmsg = msggs.message } else if (msggs.types == 1) { nmsg = '[图片]' } else if (msggs.types == 2) { nmsg = '[音频]' } else if (msggs.types == 3) { nmsg = '[位置]' } for (let i = 0; i < List.value.length; i++) { if (List.value[i].id == msggs.from) { let e = List.value[i] e.lastTime = new Date() e.msg = nmsg e.tip++ // 删除原来数据项 List.value.splice(i, 1) // 新消息插入到最顶部 List.value.unshift(e) // 更改最后一条消息时间 uni.request({ url: 'http://localhost:3461/wsb/updatetime', data: { uid: store.userid, fid: msggs.from }, method: 'POST', success: (data) => { let code = data.data.code if (code == 200) {} else { uni.showToast({ title: "服务器出错啦!", icon: "none", duration: 2000 }) } } }) } } }); }
效果:
如果还没有打开就会是未读消息,消息数加一,就在我们的头像右上角,大家可以看到小红数,当打开聊天框后小红数才会消失
4、用户端(聊天页面)
4.1、用户端uniapp是一个基于vue3的框架,首先在onMounted生命周期中判断是否链接后端
onMounted(() => { uni.connectSocket({ // 接口 url: 'ws://localhost:8082/' + user.uid, success: data => { console.log(data); } }); uni.onSocketOpen(function(res) { socketOpen = true; for (var i = 0; i < socketMsgQueue.length; i++) { sendSocketMessage(socketMsgQueue[i]); } socketMsgQueue = []; }); uni.onSocketClose(function(res) { console.log('WebSocket 已关闭!'); }); uni.onSocketError(function(res) { console.log('WebSocket连接打开失败,请检查!'); }); receiveSocketMsg() })
4.2、获取好友聊天内容,把内容存储到List数组中
const getList = () => { uni.request({ url: 'http://localhost:3461/wsb/msg', data: { uid: user.uid, fid: friend.fid }, method: 'POST', success: (data) => { let code = data.data.code if (code == 200) { let msg = data.data.query msg.reverse() if (msg.length > 0) { // console.log(msg); let oldtime = msg[0].time let imgarr = [] for (var i = 1; i < msg.length; i++) { // 时间间隔 if (i < msg.length - 1) { let t = myfun.spaceTime(oldtime, msg[i].time) if (t) { oldtime = t } msg[i].time = t } // 匹配最大时间 if (msg[i].time > oldTime.value) { oldTime.value = msg[i].time } // 补充图片地址 if (msg[i].types == 1) { msg[i].message = 'http://localhost:3461/' + msg[i].message imgarr.push(msg[i].message) } // json字符串还原 if (msg[i].types == 3) { msg[i].message = JSON.parse(msg[i].message) } // List.value.unshift(msg[i]) nextTick(() => { scrollToView.value = 'msg' + List.value[i - 1].id }) } // 两个数组拼接 List.value = msg.concat(List.value) getimg.value = imgarr.concat(getimg.value) } } else { uni.showToast({ title: "服务器出错啦!", icon: "none", duration: 2000 }) } } }) }
4.3、把你要发送的内容发给服务器,然后在看服务器是否接收
// 聊天数据发送给服务端(socket) const sendSocket = (e) => { uni.request({ url: 'http://localhost:3461/wsb/insertMsg', data: JSON.stringify({ msg: e.message, types: e.types, uid: user.uid, fid: friend.fid }), method: 'POST', success: (data) => { let code = data.data.code if (code == 200) { // console.log("添加成功"); } else { uni.showToast({ title: "服务器出错啦!", icon: "none", duration: 2000 }) } } }) // json字符串还原 if(e.types == 2){ e.message = JSON.parse(e.message) } uni.sendSocketMessage({ data: JSON.stringify({ message: e.message, types: e.types, uid: user.uid, fid: friend.fid }) }); }
4.4、服务器接收到消息后存储到用户消息里面,然后发送给客户端
// socket聊天数据接收 const receiveSocketMsg = () => { uni.onSocketMessage(function(res) { console.log('收到服务器内容123:' + res.data); const msggs = JSON.parse(res.data) if (msggs.from == friend.fid && msggs.tip==1) { let len = List.value.length; let nowTime = new Date() // 时间间隔 let t = myfun.spaceTime(oldTime.value, nowTime) if (t) { oldTime.value = t } // 判断是否加ip if (msggs.types == 1) { msggs.message = 'http://localhost:3461' + msggs.message } if ( msggs.types == 2) { msggs.message.voice = 'http://localhost:3461' + msggs.message.voice } nowTime = t let data = { fromId: msggs.from, //发送者的id imgurl: friend.fimgurl, time: nowTime, message: msggs.message, types: msggs.types, id: len }; List.value.push(data) // 图片 if (msggs.types == 1) { getimg.value.push(msggs.message) } nextTick(() => { scrollToView.value = 'msg' + len }) } }); }
表情效果:
表情可以看作是线上文字交流的重要补充。相较于面对面的沟通,人们在线上文字对话时较难感知对方的状态或情绪,而发送表情刚好可以弥补这一缺憾。
语音效果:
语音可以帮助我们快速回复对方,在发语音时,我们可以通过声音来表达自己的想法和情感,与对方进行交流和互动。
完整代码
完成效果:
服务端
/** * WebSocket模块 */ const { WebSocketServer } = require('ws'); // 端口号 const server = new WebSocketServer({ port: 8082 }); // 存储聊天信息 let chatInfo = [] // 存储在线用户 let onlineUser = [] server.on("connection", (socket, req, res) => { console.log('链接成功'); // 用户注册 // 获取当前连接websocket的用户uid let uid = req.url.substr(1) socket["id"] = uid let index = onlineUser.findIndex(val => val.id === uid) if (index == -1) { onlineUser.push(socket) } // 从客户端接收消息 socket.on("message", (data) => { // 将收到的消息转成对象格式 let msg = data.toString() let message = JSON.parse(msg) // console.log(message, '123456789'); // 通知当前用户消息发送成功 socket.send(JSON.stringify({ message: message.message, types: message.types, toid: message.fid, tip: 0 })) // 存储当前用户发送的数据 chatInfo.push({ from: uid, to: message.fid, message: message.message, types: message.types }) // console.log(onlineUser); // 根据fid匹配到接收消息的用户并发送通知 onlineUser.forEach(item => { if (item.id == message.fid) { console.log("发送消息成功"); item.send(JSON.stringify({ message: message.message, types: message.types, from: uid, tip: 1 })) } }) }); // 连接关闭后清除在线用户 socket.on('close', res => { let newUserArr = [] let newUserIds = [] for (let i = 0; i < onlineUser.length; i++) { let val = onlineUser[i] if (val.id !== uid) { newUserArr.push(val) newUserIds.push(val.id) } } onlineUser = newUserArr let User = JSON.stringify({ onlineUser: newUserIds, type: 'users' }) onlineUser.forEach((client) => client.websocket.send(User)) console.log('用户离开了'); }) socket.send(JSON.stringify({ msg: '连接已建立', type: 'system' })) });
客户端:
<!-- 详情页 --> <template> <view> <view> <view @click="back"> <image src="../../../static/icons/返回.png"></image> </view> <view> <view> {{friend.fname}} </view> </view> </view> <scroll-view scroll-y="true" scroll-with-animation="true" :scroll-into-view="scrollToView"> <view :style="{paddingBottom:inputh + 'px'}"> <view v-for="(item,index) in List" :key="index" :id="'msg'+item.id"> <view v-if="item.time != ''"> {{Datas(item.time)}} </view> <!-- 左用户 --> <view class="msg-m msg-left" v-if="item.fromId != user.uid"> <image :src="item.imgurl"></image> <!-- 文字 --> <view v-if="item.types == 0"> <view> {{item.message}} </view> </view> <!-- 图片 --> <view v-if="item.types == 1"> <image :src="item.message" mode="widthFix" @tap="previewImg(item.message)"> </image> </view> <!-- 音频 --> <view v-if="item.types == 2"> <view class="msg-text voice" :style="{width:item.message.time*4+'px'}" @tap="playVoice(item.message.voice)"> <image src="../../../static/submit/yp.png" mode=""></image> {{item.message.time}}″ </view> </view> <!-- 定位 --> <view v-if="item.types == 3"> <view @click="openLocations(item.message)"> <view> {{item.message.name}} </view> <view> {{item.message.address}} </view> <image src="../../../static/submit/dt.jpg" mode="aspectFill"></image> <!-- <map :longitude="item.message.longitude" :latitude="item.message.latitude" :markers="covers(item.message)"></map> --> </view> </view> </view> <!-- 右用户 --> <view class="msg-m msg-right" v-if="item.fromId == user.uid"> <image :src="item.imgurl"></image> <view v-if="item.types == 0"> <view> {{item.message}} </view> </view> <!-- 照片 --> <view v-if="item.types == 1"> <image :src="item.message" mode="widthFix" @tap="previewImg(item.message)"> </image> </view> <!-- 音频 --> <view v-if="item.types == 2"> <view class="msg-text voice" :style="{width:item.message.time*4+'px'}" @tap="playVoice(item.message.voice)"> {{item.message.time}}″ <image src="../../../static/submit/yp.png" mode=""></image> </view> </view> <!-- 定位 --> <view v-if="item.types == 3"> <view @click="openLocations(item.message)"> <view> {{item.message.name}} </view> <view> {{item.message.address}} </view> <!-- <map :longitude="item.message.longitude" :latitude="item.message.latitude" :markers="covers(item.message)"></map> --> <image src="../../../static/submit/dt.jpg" mode="aspectFill"></image> </view> </view> </view> </view> </view> <view></view> </scroll-view> <!-- 底部导航栏 --> <submit></submit> </view> </template> <script setup> import submit from '@/pages/buyCar/chat/submit.vue' import { onLoad, } from '@dcloudio/uni-app' import datas from '@/pages/buyCar/datas.js' import myfun from '@/pages/buyCar/myfun.js' // pinia import { useStore } from '@/store/users.js' // 获取用户id const store = useStore() // 用户信息 const user = reactive({ uid: '', uimgurl: '', uname: '' }) // 获取用户信息 const getUser = () => { user.uid = store.userid user.uimgurl = store.uimgurl user.uname = store.uname } import { nextTick, onMounted, reactive, ref } from "vue"; // 音频 const innerAudioContext = uni.createInnerAudioContext(); // 总数据 const List = ref([]) //获取所以消息 const getimg = ref([]) //获取所以图片 const oldTime = ref(0) //时间差 const scrollToView = ref('') // 让他定位到最后一条数据 const inputh = ref(72) // websocket信息存储 var socketOpen = false; var socketMsgQueue = []; // 对方的信息(前面的页面传递过来的信息) const friend = reactive({ fid: '', fimgurl: '', fname: '' }) // 进页面渲染(获取聊天数据) onLoad((options) => { friend.fid = options.id friend.fimgurl = options.img friend.fname = options.name getUser() getList() }) onMounted(() => { uni.connectSocket({ // 接口 url: 'ws://localhost:8082/' + user.uid, success: data => { console.log(data); } }); uni.onSocketOpen(function(res) { socketOpen = true; for (var i = 0; i < socketMsgQueue.length; i++) { sendSocketMessage(socketMsgQueue[i]); } socketMsgQueue = []; }); uni.onSocketClose(function(res) { console.log('WebSocket 已关闭!'); }); uni.onSocketError(function(res) { console.log('WebSocket连接打开失败,请检查!'); }); receiveSocketMsg() }) // const getList1 = () => { // let msg = datas.message() // for (let i = 0; i < msg.length; i++) { // msg[i].imgurl = '../../../static/' + msg[i].imgurl; // // 时间间隔 // if (i < msg.length - 1) { // let t = myfun.spaceTime(new Date(), msg[i].time) // if (t) { // oldTime = t // } // msg[i].time = t // } // // 补充图片地址 // if (msg[i].types == 1) { // msg[i].message = '../../../static/' + msg[i].message; // getimg.value.unshift(msg[i].message) // } // List.value.unshift(msg[i]) // nextTick(() => { // scrollToView.value = 'msg' + List.value[i - 1].tip // }) // } // // 让他定位到最后一条数据 // // console.log(List.value); // // console.log(scrollToView.value); // } const getList = () => { uni.request({ url: 'http://localhost:3461/wsb/msg', data: { uid: user.uid, fid: friend.fid }, method: 'POST', success: (data) => { let code = data.data.code if (code == 200) { let msg = data.data.query msg.reverse() if (msg.length > 0) { // console.log(msg); let oldtime = msg[0].time let imgarr = [] for (var i = 1; i < msg.length; i++) { // 时间间隔 if (i < msg.length - 1) { let t = myfun.spaceTime(oldtime, msg[i].time) if (t) { oldtime = t } msg[i].time = t } // 匹配最大时间 if (msg[i].time > oldTime.value) { oldTime.value = msg[i].time } // 补充图片地址 if (msg[i].types == 1) { msg[i].message = 'http://localhost:3461/' + msg[i].message imgarr.push(msg[i].message) } // json字符串还原 if (msg[i].types == 3) { msg[i].message = JSON.parse(msg[i].message) } // List.value.unshift(msg[i]) nextTick(() => { scrollToView.value = 'msg' + List.value[i - 1].id }) } // 两个数组拼接 List.value = msg.concat(List.value) getimg.value = imgarr.concat(getimg.value) } } else { uni.showToast({ title: "服务器出错啦!", icon: "none", duration: 2000 }) } } }) } // 接收输入框的内容 // uni.$on('inputs', (e) => { // // console.log(e); // let len = List.value.length; // let nowTime = new Date() // // 时间间隔 // let t = myfun.spaceTime(new Date(), nowTime) // if (t) { // oldTime = t // } // nowTime = t // let data = { // _id: "b", // imgurl: "../../../static/07.jpg", // time: nowTime, // message: e.message, // types: e.types, // tip: len // }; // List.value.push(data) // nextTick(() => { // scrollToView.value = 'msg' + len // }) // if (e.types == 1) { // getimg.value.push(e.message) // } // }) uni.$on('inputs', (e) => { getInput(e, user.uid, user.uimgurl, 0) console.log(e); }) // 接收消息 const getInput = (e, id, img, tip) => { // tip=0表示自己发的。tip=1表示对方发的 // socket提交 // 文字或者地图 if (e.types == 0 || e.types == 3) { sendSocket(e) } // 图片 if (e.types == 1) { getimg.value.push(e.message) // 提交图片处理 const uploadTask = uni.uploadFile({ url: 'http://localhost:3461/files/upload', //仅为示例,非真实的接口地址 filePath: e.message, name: 'file', formData: { url: 'picture', name: new Date().getTime() + user.uid + Math.ceil(Math.random() * 10), }, success: (uploadFileRes) => { // console.log(uploadFileRes); let data = { message: uploadFileRes.data, types: e.types } sendSocket(data) // let path = uploadFileRes.data // img.value.push('http://localhost:3461/'+path) // console.log(uploadFileRes.data); }, }); uploadTask.onProgressUpdate((res) => { // console.log('上传进度' + res.progress); // console.log('已经上传的数据长度' + res.totalBytesSent); // console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend); // 测试条件,取消上传任务。 // if (res.progress > 50) { // uploadTask.abort(); // } }); } // 音频 if (e.types == 2) { // 提交音频处理 const uploadTask = uni.uploadFile({ url: 'http://localhost:3461/files/upload', //仅为示例,非真实的接口地址 filePath: e.message.voice, name: 'file', formData: { url: 'voice', name: new Date().getTime() + user.uid + Math.ceil(Math.random() * 10), }, success: (uploadFileRes) => { // console.log(uploadFileRes); let data = { // json转json字符串 message: JSON.stringify({ voice:uploadFileRes.data, time:e.message.time }), types: e.types } sendSocket(data) // let path = uploadFileRes.data // img.value.push('http://localhost:3461/'+path) // console.log(uploadFileRes.data); }, }); uploadTask.onProgressUpdate((res) => { // console.log('上传进度' + res.progress); // console.log('已经上传的数据长度' + res.totalBytesSent); // console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend); // 测试条件,取消上传任务。 // if (res.progress > 50) { // uploadTask.abort(); // } }); } // 前端处理 let len = List.value.length; let nowTime = new Date() // 时间间隔 let t = myfun.spaceTime(oldTime.value, nowTime) if (t) { oldTime.value = t } nowTime = t // json字符串还原 if(e.types == 3){ e.message = JSON.parse(e.message) } let data = { fromId: id, imgurl: img, time: nowTime, message: e.message, types: e.types, id: len }; List.value.push(data) nextTick(() => { scrollToView.value = 'msg' + len }) } // 聊天数据发送给服务端(socket) const sendSocket = (e) => { uni.request({ url: 'http://localhost:3461/wsb/insertMsg', data: JSON.stringify({ msg: e.message, types: e.types, uid: user.uid, fid: friend.fid }), method: 'POST', success: (data) => { let code = data.data.code if (code == 200) { // console.log("添加成功"); } else { uni.showToast({ title: "服务器出错啦!", icon: "none", duration: 2000 }) } } }) // json字符串还原 if(e.types == 2){ e.message = JSON.parse(e.message) } uni.sendSocketMessage({ data: JSON.stringify({ message: e.message, types: e.types, uid: user.uid, fid: friend.fid }) }); } // socket聊天数据接收 const receiveSocketMsg = () => { uni.onSocketMessage(function(res) { console.log('收到服务器内容123:' + res.data); const msggs = JSON.parse(res.data) if (msggs.from == friend.fid && msggs.tip==1) { let len = List.value.length; let nowTime = new Date() // 时间间隔 let t = myfun.spaceTime(oldTime.value, nowTime) if (t) { oldTime.value = t } // 判断是否加ip if (msggs.types == 1) { msggs.message = 'http://localhost:3461' + msggs.message } if ( msggs.types == 2) { msggs.message.voice = 'http://localhost:3461' + msggs.message.voice } nowTime = t let data = { fromId: msggs.from, //发送者的id imgurl: friend.fimgurl, time: nowTime, message: msggs.message, types: msggs.types, id: len }; List.value.push(data) // 图片 if (msggs.types == 1) { getimg.value.push(msggs.message) } nextTick(() => { scrollToView.value = 'msg' + len }) } }); } // 输入框的高度 uni.$on('heights', (e) => { // console.log(e); inputh.value = e.msg // console.log(inputh .value); goBottom() }) // 滚动到底部 const goBottom = () => { scrollToView.value = '' nextTick(() => { let len = List.value.length - 1 scrollToView.value = 'msg' + List.value[len].id }) } // 处理时间 const Datas = (data) => { return myfun.dateTime(data) } // 返回上一页 const back = () => { uni.navigateBack({ delta: 1 }); } // 图片预览 const previewImg = (e) => { let index = 0; // 点击图片后通过for循环查找出第几张是该点击得图片 for (let i = 0; i < getimg.value.length; i++) { if (getimg.value[i] == e) { index = i; } } // uni中的api,图片预览 uni.previewImage({ current: index, // 点击图片看第几张 urls: getimg.value, longPressActions: { itemList: ['发送给朋友', '保存图片', '收藏'], success: function(data) { console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片'); }, fail: function(err) { console.log(err.errMsg); } } }); } // 音频播放 const playVoice = (e) => { innerAudioContext.src = e; innerAudioContext.play(); } // 获取位置 const covers = (e) => { let map = [{ latitude: e.latitude, longitude: e.longitude, iconPath: '../../../static/submit/dw1.png' }] return map } // 导航定位 const openLocations = (e) => { uni.openLocation({ latitude: e.latitude, longitude: e.longitude, name: e.name, address: e.address, success: function() { console.log('success'); } }); } </script> <style> .chatroom { width: 100%; height: 100%; background: rgba(0, 0, 0, 0.1); .top-bar { position: fixed; z-index: 1001; top: 0; left: 0; width: 100%; height: 120rpx; padding-top: var(--status-bar-height); background: #fff; border-bottom: 1rpx solid #ccc; .top-bar-left { float: left; padding-left: 5rpx; image { margin-top: 10rpx; width: 68rpx; height: 88rpx; border-radius: 16rpx; } } .top-bar-center { float: auto; text-align: center; .title { font-size: 40rpx; color: #000; line-height: 120rpx; } } } } .chat { height: 100%; .padbt { width: 100%; height: 40rpx; } .chat-main { padding-left: 32rpx; padding-right: 32rpx; padding-top: 160rpx; display: flex; flex-direction: column; } .chat-ls { .chat-time { font-size: 24rpx; color: rgba(39, 40, 50, 0.3); line-height: 34rpx; padding: 25rpx 0; text-align: center; } .msg-m { display: flex; padding: 20rpx 0; .user-img { flex: none; width: 80rpx; height: 80rpx; border-radius: 20rpx; } .massage { flex: none; max-width: 480rpx; } .msg-text { font-size: 32rpx; color: rgba(39, 40, 50, 1); line-height: 44rpx; padding: 18rpx 24rpx; } .msg-img { max-width: 400rpx; border-radius: 20rpx; } .msg-map { background-color: #fff; width: 460rpx; height: 284rpx; overflow: hidden; .map-name { font-size: 32rpx; color: rgba(39, 40, 50, 1); line-height: 44rpx; padding: 18rpx 24rpx 0 24rpx; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; } .map-address { font-size: 26rpx; color: rgba(39, 40, 50, 0.4); padding: 0rpx 24rpx; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; } .map { padding-top: 8rpx; width: 460rpx; height: 190rpx; } } .voice { min-width: 80rpx; max-width: 400rpx; } .voice-img { width: 28rpx; height: 36rpx; } } .msg-left { flex-direction: row; .msg-text { margin-left: 16rpx; background: #fff; border-radius: 0rpx 20rpx 20rpx 20rpx; } .msg-img { margin-left: 16rpx; } .msg-map { margin-left: 16rpx; border-radius: 0rpx 20rpx 20rpx 20rpx; } .voice { width: 200rpx; text-align: left; } .voice-img { width: 28rpx; height: 36rpx; padding-top: 4rpx; } } .msg-right { flex-direction: row-reverse; .msg-text { margin-right: 16rpx; background: #fff260; border-radius: 20rpx 0rpx 20rpx 20rpx; } .msg-img { margin-right: 16rpx; } .msg-map { margin-right: 16rpx; border-radius: 20rpx 0rpx 20rpx 20rpx; } .voice { width: 200rpx; text-align: right; } .voice-img { float: right; transform: rotate(180deg); width: 28rpx; height: 36rpx; padding-top: 4rpx; } } } } </style>
猜你喜欢
- 【Vue】Antd Pro Vue的使用(一)—— 安装及运行
- 前言Ant Design Pro 是一个企业级中后台前端/设计解决方案,致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。AntDesignVue与react版本有几乎相同的布局AntDesignPro React版本:开箱即用的中台前端/设计解决方案 - Ant Design ProAntDesign组件:Ant Design - 一套企业级 UI 设计语言和 React 组件库Ant
- 【Vue】Vue中对axios进行封装的最佳实践
- vue是当前前端开发中最常用的框架之一,而ajax请求又是前端开发中非常关键的一环。为了方便开发者使用,vue社区中出现了许多对ajax请求库axios进行封装的实践。本文将介绍vue中对axios进行封装的最佳实践,帮助您更好地理解如何在vue项目中使用axios。封装axios在Vue项目中,我们需要把axios进行封装以方便使用。这里介绍一个标准的axios封装:import axios from 'axios' import sto
- 【Vue】Antd Pro Vue的使用(五)—— 多文件上传回显问题
- 需求: 多文件上传 ,上传的时候绑定fileList回显问题: 上传成功了,也拿到了后台返回的数据,但是onchang监听的时候,file的状态一直是uploading原因:onchange 只触发了一次解决: 使用单文件上传时@change事件会至少触发两次,一次file.status=uploading,最后一次要么是done或者error,handleUpload1(info) { if (info
- 【Vue】Antd Pro Vue的使用(三)—— table列表的使用
- 用了几天ant design pro vue,发现vue2真的不是很好用,各种写法好麻烦。还有研究组件时,一定要看低版本的组件,高版本都是vue3的,并不适用。vue2版本组件位置:https://1x.antdv.com/components/alert-cn/ 作为后台管理端,用到最多的就是table列表,官网给的有预览但是自己上手的时候有事另外一回事了,首先就是接口请求的数据结果,官网并没有介绍接口应该返回什么样的数据结构,导致接口成功请求到数据,但table就是无法正常显示,最终参考de
- 【Vue】vue3比vue2好在哪里
- vue 3 优于 vue 2 的关键优势包括:性能提升:响应式系统重写,优化更新速度虚拟 dom 优化,提高渲染效率代码组织和可维护性:组合式 api,提升可维护性teleport 和 suspense,提高代码灵活性和可读性开发者体验:更好的调试工具,简化调试过程typescript 2.7 支持,增强代码提示和类型检查Vue 3 与 Vue 2 的优势对比核心性能提升响应式系统重写:Vue 3 引入 Reactivity API,优化了响应式系统的性能,提升了更新速度。虚拟 DOM 优化:采
- 【Vue】Vue定义全局变量的方法
- 在Vue项目中我们需要使用许多的变量来维护数据的流向和状态,这些变量可以是本地变量、组件变量、父子组件变量等,但这些变量都是有局限性的。在一些场景中,可能需要在多个组件中共享某个变量,此时全局变量就派上了用场。定义全局变量的方法1. 使用Vue.prototype定义全局变量通过在 vue 的原型上定义属性,可以在所有组件中访问该属性。在main.js定义全局变量// main.jsVue.prototype.baseUrl = "https://www.example.com/api
- 【VUE】如何查看前端的vue项目是vue2还是vue3项目
- 1. 检查package.json文件在项目的根目录下,打开package.json文件,查找dependencies或devDependencies部分中的vue条目。版本号将告诉你是Vue 2还是Vue 3。例如:Vue 2.x: "vue": "^2.x.x"Vue 3.x: "vue": "^3.x.x"2. 使用Vue Devtools如果项目正在运行,并且你已经安装了Vue Devtools(Vue开发者
- 【Vue】Vue3 开发实战分享——打印插件 Print.js 的使用(Vue3 + Nodejs + Print.js 实战)以及 el-table 与 el-pagination 的深入使用(下)
- 文章目录📋前情回顾&前言🎯关于 el-table1️⃣获取每行对应的内容数据2️⃣行内数据判断处理(过滤)3️⃣对表格内容的索引🧩项目中延申使用🎯关于 el-pagination1️⃣显示总条数与分页展示2️⃣跳转页和页码样式3️⃣设置为中文🧩项目中延申使用📝最后📋前情回顾&前言上一篇文章(Vue3 开发实战分享——打印插件 Print.js 的使用(Vue3 + Nodejs + Print.js 实战)以及 el-table 与 el-pagination 的深
栏目分类全部>