简介:
本篇文章带来的是仿照微信设计的聊天界面,主要涉及到三个文件;下方已有介绍,代码块中也有相关说明,直接复制然后就可使用;注意获取消息的websoket和拉取历史消息的接口还是需要自己去写,下方只做大致参考;具体消息格式下方代码块中已列出
一、聊天主界面chatIndex.vue
<template>
<view class="chat">
<scroll-view
class="scroll-view"
:style="{height: `${windowHeight-inputHeight}rpx`}" <-- 根据键盘高度调整聊天界面高低 -->
id="scrollview"
scroll-y="true"
:scroll-top="scrollTop"
@scrolltoupper="topRefresh"
@click="touchClose"
>
<view id="msglistview" class="chat-body">
<u-loading-icon v-if="loading" />
<view v-for="(item,index) in msgList" :key="index">
<view class="msg-time" v-if="item.isShowTime">
{{changeTime(item.sendTime)}}
view>
<view class="item self" v-if="item.sendUserId == userId">
<view class="msg-menu menu-right" :style="{display: showBoxId==item.id?'block':'none'}">
<view class="tr-icon tr-icon-right">view>
<ChatMsgMenu :msgUserId="item.sendUserId" :msgId="item.id" :content="item.content" :msgSortId="index" :time="item.sendTime" @cancelMsg="cancelMsg"/>
view>
<view @longpress="showBoxId=item.id">
<view class="content-text right" v-if="item.type=='text'">
{{item.content}}
view>
<view class="content-text right" v-else-if="item.type=='voice'">
<view style="display: flex;" @click="playSound(item.content)">
<text>{{ item.soundTIme }}''text>
<image style="width: 42rpx;height: 42rpx;" :src="imgConf.replayChange"/>
view>
view>
<view class="content-img" v-else-if="item.type=='img'">
<image class="img-style" :src="item.content" mode="widthFix" :lazy-load="true"/>
view>
<view class="content-video" v-else>
<video class="video-style" :src="item.content" />
view>
view>
<image class="avatar" :src="item.wxUser.headImg" />
view>
<view class="item Ai" v-else>
<image class="avatar" :src="item.wxUser.headImg" />
<view @longpress="showBoxId=item.id">
<view class="content-text left" v-if="item.type=='text'">
{{item.content}}
view>
<view class="content-text le ft" v-else-if="item.type=='voice'">
<view style="display: flex;" @click="playSound(item.content)">
<text>{{ item.soundTIme }}''text>
<image style="width: 42rpx;height: 42rpx;" :src="imgConf.replayChange"/>
view>
view>
<view class="content-img" v-else-if="item.type=='img'">
<image class="img-style" :src="item.content" mode="widthFix" :lazy-load="true"/>
view>
<view class="content-video" v-else>
<video class="video-style" :src="item.content" />
view>
view>
<view class="msg-menu menu-left" :style="{display: showBoxId==item.id?'block':'none'}">
<view class="tr-icon tr-icon-left">view>
<ChatMsgMenu :msgUserId="item.sendUserId" :msgId="item.id" :content="item.content" :msgSortId="index" :time="item.sendTime" @cancelMsg="cancelMsg"/>
view>
view>
view>
view>
scroll-view>
<view class="chat-bottom" :style="{height: `${inputHeight}rpx`}">
<view class="send-msg" :style="{bottom:`${keyboardHeight}rpx`}">
<view class="uni-textarea">
<image class="icon-style" :src="changeLogUrl" @click="changeInputType"/>
<view class="out_textarea_box">
<textarea
placeholder-class="textarea_placeholder"
:style="{textAlign:(textareaConf.disabled?'center':'')}"
v-model="chatMsg"
maxlength="250"
confirm-type="send"
auto-height
:placeholder="textareaConf.text"
:show-confirm-bar="false"
:adjust-position="false"
:disabled="textareaConf.disabled"
@confirm="handleSend"
@linechange="sendHeight"
@focus="focus" @blur="blur"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
/>
view>
<image class="icon-style" :src="imgConf.emoji" @click="handleSend"/>
<image class="icon-style" :src="imgConf.more" @click="moreMenu"/>
view>
view>
<view :style="{display:showMoreMenu?'block':'none'}" class="more-menu">
<view class="inner-box">
<view class="menu" @click="sendFile('choose','')">
<view>
<image class="i-style" :src="imgConf.sendphoto">image>
<view class="t-style">照片view>
view>
view>
<view class="menu" @click="sendFile('shoot','')">
<view>
<image class="i-style" :src="imgConf.takePhoto">image>
<view class="t-style">拍摄view>
view>
view>
view>
view>
view>
<view class="voice-mask" v-show="voice.mask">
<view class="inner-mask">
<view class="voice-progress-box" :style="{width:`${progressNum}`+'rpx'}">
<view class="third-icon"/>
<view class="progress-num">
{{ voice.length }}s
view>
view>
<view class="cancel-btn" :class="{cancelBtn:voice.cancel}">
<image style="width: 60rpx;height: 60rpx;" src="http://116.205.133.116:8080/static/app/logo/publicLogo/cancelSend.png">image>
view>
<view class="show-tips">
上滑取消发送
view>
<view class="bottom-area">
<image class="img-style" :src="imgConf.voiceBtn" />
view>
view>
view>
view>
template>
<script>
import timeMethod from "@/tools/timeMethod.js"; //时间工具类
export default{
data() {
return {
imgConf: { //界面icon图片配置,可自定义
emoji: "http://xx/static/app/logo/publicLogo/emoji.png",
more: "http://xx/static/app/logo/publicLogo/more.png",
sendphoto: "http://xx/static/app/logo/publicLogo/sendPhoto.png",
sendVideo: "http://xx/static/app/logo/publicLogo/video.png",
takePhoto: "http://xx/static/app/logo/publicLogo/takePhoto.png",
voiceBtn: "http://xx/static/app/logo/publicLogo/voiceBtn.png",
keyboard: "http://xx/static/app/logo/publicLogo/keyborad.png",
speak: "http://xx/static/app/logo/publicLogo/speak.png",
replayChange: "http://xx/static/app/logo/publicLogo/replay.png",
replay: "http://xx/static/app/logo/publicLogo/replay.png",
replaing: "http://xx/static/app/logo/publicLogo/replaing.png"
},
changeLogUrl: "http://xx/static/app/logo/publicLogo/speak.png",
loading: false,
keyboardHeight:0,
bottomHeight: 0,
scrollTop: 0,
chatMsg: "",
userId: "", //自己的userId
userHeadImg: "", //自己的头像
toUserId: "", //聊天朋友的userId
toUserHeadImg: "", //聊天朋友的头像
pageSize: 20, //分页数量
pageNum: 1, //分页开始数
returnPageNum: "", //接口返回的聊天信息的总页数
msgList: [], //聊天消息list
judgeScrollToBottom: true,
startTime: "",
msgID: 0,
showBoxId: "",
showBoxUserId: "",
showMoreMenu: false,
textareaConf: {
disabled: false,
text: ""
},
voice: { //语音录制界面配置
mask: false, //遮罩层
length: 0, //语音录制时长
cancel: false, //是否取消发送
startX: "", //获取
startY: "",
timer: "",
recordInstance: "", //uni语音录制实例
finished: false //是否已结束录制
},
msgConf: {
showTimeSpace: 120 //消息隔多长时间才展示
}
}
},
updated(){
//页面更新时调用聊天消息定位到最底部
if (this.judgeScrollToBottom) {
this.scrollToBottom();
}
},
computed: {
//获取窗口页面高度
windowHeight() {
return this.rpxTopx(uni.getSystemInfoSync().windowHeight);
},
// 键盘弹起来的高度+发送框高度
inputHeight(){
return this.bottomHeight+this.keyboardHeight;
},
//语音录制时进度条
progressNum() {
return this.voice.length*2 + 250;
}
},
onLoad(e){
//监听键盘高度
uni.onKeyboardHeightChange(res => {
this.keyboardHeight = this.rpxTopx(res.height);
if(this.keyboardHeight<=0) {
this.keyboardHeight = 0;
this.showMoreMenu = false;
}
});
//获取自己userId
this.userId = uni.getStorageSync("userId");
//获取好友UserId
this.toUserId = e.userId;
//创建录音实例
this.voice.recordInstance = uni.getRecorderManager();
//调用websocket进行监听
this.webSocket();
this.getUserInfo();
this.getMessage();
this.readedMsg();
},
onUnload() {
//关闭socket
uni.closeSocket({
code: 200,
success() {
console.log("正常关闭")
}
})
},
methods: {
//websocket实例
webSocket() {
//消息监听
uni.onSocketMessage((res)=>{
let data = JSON.parse(res.data);
this.msgList.push(data.msg);
})
},
//下拉刷新,分页用的
topRefresh() {
if (this.pageNum<this.returnPageNum) {
this.pageNum++;
this.judgeScrollToBottom = false;
this.loading = true;
//调用消息接口,拉去消息
this.getMessage();
}
},
//获取聊天消息
getMessage() {
//this.$request("/msg/getChatMessage","POST",
//{"sendUserId": this.userId,
//"acceptUserId": this.toUserId,
//"pageSize": this.pageSize,
//"pageNum": this.pageNum},{"Content-Type":"application/x-www-form-urlencoded"}).then(res=>{
//this.returnPageNum = res.data.data.pagesNum; //获取消息的总页数
//this.showMsgTime(res.data.data.data); //处理消息信息,用于在消息列表上展示消息时间
//res.data.data.data的数据消息为以下格式:
//消息为四种类型-文本(text)/图片(img)/视频(video)/语音(voice),通过type来区别,content如果是text类型则直接存的文本信息,如果是其他三种则直接存链接形式
let data = [
{
"id": 29,
"sendUserId": "20220328001",
"acceptUserId": "20220328007",
"type": "text",
"content": "555",
"sendTime": "2023-11-11 13:48:25",
"wxUser": {
"nickName": "Andy",
"headImg": "http://xxx/static/images/779e2a63-9d30-4db1-bace-a1d50ecb3866.jpg"
}
},
{
"id": 28,
"sendUserId": "20220328001",
"acceptUserId": "20220328007",
"type": "voice",
"content": "http://xxx/static/images/779e2a63-9d30-4db1-bace-a1d50ecb3866.mp3",
"sendTime": "2023-11-11 13:48:24",
"wxUser": {
"nickName": "Andy",
"headImg": "http://xxx/static/images/779e2a63-9d30-4db1-bace-a1d50ecb3866.jpg"
}
},
{
"id": 29,
"sendUserId": "20220328001",
"acceptUserId": "20220328007",
"type": "img",
"content": "http://xxx/static/images/779e2a63-9d30-4db1-bace-a1d50ecb3866.jpg",
"sendTime": "2023-11-11 13:48:25",
"wxUser": {
"nickName": "Andy",
"headImg": "http://xxx/static/images/779e2a63-9d30-4db1-bace-a1d50ecb3866.jpg"
}
},]
this.showMsgTime(data);
this.loading = false;
//})
},
//消息时间展示
showMsgTime(data) {
data.forEach(e=>{
e.isShowTime = false; //时间显示打标
e.sendTime = timeMethod.timeFormat(e.sendTime,"T");
this.msgID++; //消息id计数,定位消息list的索引
if (this.startTime!="") { //第一条消息前面没时间,排出掉
if (Math.abs(timeMethod.calculateTime(e.sendTime,this.startTime))/1000 > this.msgConf.showTimeSpace) { //计算消息时间间隔大于120秒
this.msgList.slice(0 - this.msgID)[0].isShowTime = true; //注入打标数据
}
}
this.startTime = e.sendTime; //每次循环记住该条消息时间,用于计算消息之间时间间隔
this.msgList.unshift(e); //处理好数据后push进消息list
})
//消息列表最上面一条显示时间
if (this.pageNum == this.returnPageNum) {
this.msgList[0].isShowTime = true;
}
},
//时间转变
changeTime(time) {
let space = (new Date(timeMethod.timeFormat(time,"T")) - new Date(timeMethod.getNowTime().split("T")[0]+"T00:00:00"))/(1000*60*60*24);
let Time =timeMethod.timeFormat(time," ").split(" ");
let week = timeMethod.getDateToWeek(time);
//当天
if (space > 0 && space < 1) {
return Time[1].slice(0,5);
}
//昨天
else if (space > -1 && space < 0) {
return "昨天 " + Time[1].slice(0,5);
}
//星期
else if (space < -1 && Math.abs(space) < timeMethod.getDateToWeek(timeMethod.getNowTime()).weekID - 1) {
return week.weekName + " " + Time[1].slice(0,5);
}
//日期
else {
return Time[0].slice(5,10) + " " + Time[1].slice(0,5);
}
},
//获取用户信息,这儿获取聊天好友的用户信息
getUserInfo() {
this.$request("/sys/getUserName","POST",{"userId":this.userId,"toUserId":this.toUserId},
{"Content-Type":"application/x-www-form-urlencoded"}).then(res=>{
let data = res.data.data;
this.userHeadImg = data[0].headImg;
this.toUserHeadImg = data[1].headImg;
uni.setNavigationBarTitle({
title: data[1].nickName
})
})
},
//输入框聚焦
focus(){
this.scrollToBottom();
},
//输入框取消聚焦
blur(){
this.scrollToBottom();
},
// px转换成rpx
rpxTopx(px){
let deviceWidth = uni.getSystemInfoSync().windowWidth;
let rpx = ( 750 / deviceWidth ) * Number(px);
return Math.floor(rpx);
},
// 监视聊天发送栏高度
sendHeight(){
setTimeout(()=>{
let query = uni.createSelectorQuery();
query.select('.send-msg').boundingClientRect();
query.exec(res =>{
this.bottomHeight = this.rpxTopx(res[0].height);
})
},200)
},
// 滚动至聊天底部
scrollToBottom(e){
setTimeout(()=>{
let query = uni.createSelectorQuery().in(this);
query.select('#scrollview').boundingClientRect();
query.select('#msglistview').boundingClientRect();
query.exec((res) =>{
if(res[1].height > res[0].height){
this.scrollTop = this.rpxTopx(res[1].height - res[0].height);
}
})
},200);
},
// 发送消息
handleSend() {
this.judgeScrollToBottom = true;
this.pageNum = 1;
//如果消息不为空
if(this.chatMsg.length!==0){
this.$request("/msg/sendMsg","POST",{
"sendUserId":this.userId,
"acceptUserId":this.toUserId,
"type": "text",
"content":this.chatMsg}).then(res=>{
if (res.data.status=="ok") {
//发送消息成功后会返回刚刚发送的消息数据,然后借push进消息list
this.msgList.push(res.data.data);
this.chatMsg = "";
}
})
}
},
//接收消息或发送消息时间显示
showTime() {
let time = timeMethod.getNowTime();
if (timeMethod.calculateTime(time,this.msgList.slice(-1)[0].sendTime)/1000 > this.msgConf.showTimeSpace) {
return true;
}else {
return false;
}
},
//已读消息
readedMsg() {
this.$request("/msg/readedMsg","GET",{"sendUserId":this.toUserId,"acceptUserId":this.userId})
},
//语音播放
playSound(url) {
this.imgConf.replayChange = this.imgConf.replaing;
let music = null;
music = uni.createInnerAudioContext();
music.src = url;
music.play();
music.onEnded(()=>{
music = null;
this.imgConf.replayChange = this.imgConf.replay;
})
},
//msglist索引-删除消息list中的元素
cancelMsg(id) {
//自定义的移除list元素的方法
Array.prototype.remove = function (dx) {
if (isNaN(dx) || dx > this.length) { return false; }
for (var i = 0, n = 0; i < this.length; i++) {
if (this[i] != this[dx]) {
this[n++] = this[i]
}
}
this.length -= 1
}
//直接调用移除消息list中的元素
this.msgList.remove(id);
},
//语音图标切换
changeInputType() {
if (this.changeLogUrl == this.imgConf.speak) {
this.changeLogUrl = this.imgConf.keyboard;
this.textareaConf.disabled = true;
this.textareaConf.text = "按住说话";
this.chatMsg = "";
} else {
this.changeLogUrl = this.imgConf.speak;
this.textareaConf.disabled = false;
this.textareaConf.text = "";
}
},
//全局点击关闭
touchClose() {
this.showBoxId = "";
this.showMoreMenu = false;
this.keyboardHeight = 0;
},
//更多菜单
moreMenu() {
this.keyboardHeight = 300;
let timer = setTimeout(()=>{this.showMoreMenu = true;},100);
},
// 开始录制语音
handleTouchStart(e){
var that = this;
if (this.textareaConf.disabled) {
that.voice.finished = false; //手指离开按钮打标
uni.getSetting({
success(res) {
if (res.authSetting['scope.record']===undefined) {
console.log("第一次授权")
} else if (!res.authSetting['scope.record']) {
uni.showToast({
icon: "none",
title: "点击右上角···进入设置开启麦克风授权!",
duration: 2000
})
} else {
that.voice.recordInstance.start();
that.voice.mask = true;
that.voice.isRecord = true;
that.voice.length = 1;
that.voice.startX = e.touches[0].pageX;
that.voice.startY = e.touches[0].pageY;
that.voice.timer = setInterval(() => {
that.voice.length += 1;
if(that.voice.length >= 60) {
clearInterval(that.voice.timer);
that.handleTouchEnd();
}
},1000)
//判断先结束按钮但是录制才开始时不会结束录制的条件;因为获取授权这儿存在延时;所以结束录制时可能还没开始录制
if (that.voice.finished && that.voice.mask) {
that.handleTouchEnd();
}
}
}
})
}
},
// 语音录制时滑动事件
handleTouchMove(e){
if (this.textareaConf.disabled) {
if (this.voice.startY - e.touches[0].pageY >100) {
this.voice.cancel = true;
}else {
this.voice.cancel = false;
}
}
},
// 语音录制结束
handleTouchEnd(){
if (this.textareaConf.disabled) {
this.voice.finished = true;
this.voice.mask = false;
clearInterval(this.voice.timer);
this.voice.recordInstance.stop();
this.voice.recordInstance.onStop((res) => {
const message = {
voice:res.tempFilePath,
length:this.voice.length
}
if (!this.voice.cancel) {
if (this.voice.length>1) {
this.sendFile("voice",message);
} else {
uni.showToast({
icon: 'none',
title: "语音时间太短",
duration: 1000
})
}
}else {
this.voice.cancel = false;
}
})
}
},
//发送文件
sendFile(type,data) {
var that = this;
if (type=="choose") {
uni.chooseMedia({
count: 1,
mediaType: ['image', 'video'],
sourceType: ['album'],
maxDuration: 30,
success(res) {
let type = 'img';
if (res.tempFiles[0].fileType=='image') {
type = 'img'
} else {
type = 'video'
}
that.uploadFile(res.tempFiles[0].tempFilePath,type)
}
})
} else if (type=="shoot") {
uni.chooseMedia({
count: 1,
mediaType: ['image', 'video'],
sourceType: ['camera'],
maxDuration: 30,
success(res) {
let type = 'img';
if (res.tempFiles[0].fileType=='image') {
type = 'img'
} else {
type = 'video'
}
that.uploadFile(res.tempFiles[0].tempFilePath,type)
}
})
} else {
that.uploadFile(data.voice,'voice')
}
},
uploadFile(path,type) {
var that = this;
let data = {"sendUserId":this.userId,"acceptUserId":this.toUserId,"type":type};
if (type=='voice') {
data = {"sendUserId":this.userId,"acceptUserId":this.toUserId,"type":type,"time":this.voice.length};
}
uni.uploadFile({
url: '接口地址',
filePath: path,
name: 'file',
formData: data,
header: {"token": this.$store.state.token},
success(res) {
let newMsg = JSON.parse(res.data)
//上传成功后,接口会返回消息数据 格式与上方的消息时一样的,然后把返回的数据push到消息list中
that.msgList.push(newMsg.data)
}
})
}
}
}
script>
<style lang="scss">
$chatContentbgc: #C2DCFF;
$sendBtnbgc: #4F7DF5;
center {
display: flex;
align-items: center;
justify-content: center;
}
/* 聊天消息 */
.chat {
.topTabbar {
width: 100%;
height: 90rpx;
line-height: 90rpx;
display: flex;
margin-top: 80rpx;
justify-content: space-between;
.icon {
margin-left: 20rpx;
}
.text {
margin: auto;
font-size: 16px;
font-weight: 700;
}
.button {
width: 10%;
margin: auto 20rpx auto 0rpx;
}
}
.scroll-view {
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
color: transparent;
z-index: 0;
}
background-color: #F6F6F6;
.chat-body {
display: flex;
flex-direction: column;
padding-top: 23rpx;
.self {
justify-content: flex-end;
position: relative;
}
.Ai {
position: relative;
}
.item {
display: flex;
padding: 23rpx 30rpx;
.right {
background-color: $chatContentbgc;
}
.left {
background-color: #FFFFFF;
}
.right::after {
position: absolute;
display: inline-block;
content: '';
width: 0;
height: 0;
left: 100%;
top: 10px;
border: 12rpx solid transparent;
border-left: 12rpx solid $chatContentbgc;
}
.left::after {
position: absolute;
display: inline-block;
content: '';
width: 0;
height: 0;
top: 10px;
right: 100%;
border: 12rpx solid transparent;
border-right: 12rpx solid #FFFFFF;
}
.content-text {
position: relative;
max-width: 486rpx;
border-radius: 8rpx;
word-wrap: break-word;
padding: 24rpx 24rpx;
margin: 0 24rpx;
border-radius: 5px;
font-size: 32rpx;
font-family: PingFang SC;
font-weight: 500;
color: #333333;
line-height: 42rpx;
}
.content-img {
margin: 0 24rpx;
}
.content-video {
margin: 0 24rpx;
}
.img-style {
width: 400rpx;
height: auto;
border-radius: 10rpx;
}
.video-style {
width: 400rpx;
height: 400rpx;
}
.avatar {
display: flex;
justify-content: center;
width: 78rpx;
height: 78rpx;
background: $sendBtnbgc;
border-radius: 50rpx;
overflow: hidden;
image {
align-self: center;
}
}
.msg-menu {
min-width: 100rpx;
height: 100rpx;
display: none;
background: #383838;
position: absolute;
border-radius: 10rpx;
z-index: 100;
.tr-icon {
position: absolute;
top: 100rpx;
width: 0;
height: 0;
border: 15rpx solid transparent;
border-top: 15rpx solid #383838;
}
.tr-icon-left {
left: 15rpx;
}
.tr-icon-right {
right: 15rpx;
}
}
.menu-left {
top: -100rpx;
left: 120rpx;
}
.menu-right {
top: -100rpx;
right: 120rpx;
}
}
}
.msg-time {
font-size: 24rpx;
text-align: center;
color: #737373;
}
}
.chat-bottom {
width: 100%;
height: auto;
background: #F4F5F7;
transition: all 0.25s ease;
.send-msg {
display: flex;
align-items: flex-end;
padding: 16rpx 30rpx;
width: 100%;
min-height: 150rpx;
position: fixed;
bottom: 0;
background: #fff;
transition: all 0.25s ease;
.uni-textarea {
width: 100%;
padding-bottom: 40rpx;
display: flex;
align-items: center;
.icon-style {
width: 60rpx;
height: 60rpx;
padding: 0rpx 10rpx ;
}
.out_textarea_box {
width:65%;
min-height: 80rpx;
max-height: 200rpx;
border-radius: 40rpx;
background: #f1f1f1;
display: flex;
justify-content: center;
align-items: center;
textarea {
width:86%;
min-height: 42rpx;
max-height: 200rpx;
background: #f1f1f1;
font-size: 32rpx;
font-family: PingFang SC;
color: #333333;
}
}
}
}
}
.more-menu {
width: 100%;
min-height: 300rpx;
margin-top: 150rpx;
display: none;
position: fixed;
bottom: 0rpx;
.inner-box {
width: 98%;
height: 280rpx;
margin: 10rpx 1%;
display: flex;
.menu {
width: 120rpx;
height: 130rpx;
background: #ffffff;
margin: 20rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
.i-style {
width: 80rpx;
height: 80rpx;
}
.t-style {
font-size: 22rpx;
font-weight: 600;
text-align: center;
}
}
}
}
.voice-mask{
position:fixed;
top:0;
right:0;
bottom:0;
left:0;
background-color: rgba(0,0,0,0.8);
.inner-mask {
display: flex;
flex-direction: column;
align-items: center;
.voice-progress-box {
min-width: 250rpx;
height: 150rpx;
margin-top: 60%;
border-radius: 50rpx;
background: #4df861;
position: relative;
@extend center;
.third-icon {
width: 0;
height: 0;
border: 15rpx solid transparent;
border-top: 15rpx solid #4df861;
position: absolute;
top: 100%;
left: 45%;
}
.progress-num {
font-size: 50rpx;
font-weight: 600;
}
}
.cancel-btn {
width: 120rpx;
height: 120rpx;
clip-path: circle();
margin-top: 50%;
background: #080808;
@extend center;
}
.cancelBtn {
width: 150rpx;
height: 150rpx;
}
.show-tips {
width: 100%;
margin-top: 80rpx;
text-align: center;
color: white;
animation: 4s opacity2 1s infinite;
font-size: 30rpx;
font-weight: 400;
font-family: sans-serif;
}
@keyframes opacity2{
0%{opacity:0}
50%{opacity:.8;}
100%{opacity:0;}
}
.bottom-area {
position: fixed;
bottom: 0rpx;
width: 100%;
height:190rpx;
border-top: #BABABB 8rpx solid;
border-radius: 300rpx 300rpx 0 0;
background-image: linear-gradient(#949794,#e1e3e1);
@extend center;
.img-style {
width: 50rpx;
height: 50rpx;
}
}
}
}
}
view,button,text,input,textarea {
margin: 0;
padding: 0;
box-sizing: border-box;
}
//去除scroll-view的滑动条
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
style>
二、timeMethod.js 工具类,在聊天界面会被引入
class TimeMethod {
constructor() {}
//日期格式化
addZero(data) {
if (parseInt(data) < 10) {
return "0" + String(data);
}
return data;
}
/**
* 获取当前日期
*/
getNowTime() {
var myDate = new Date();
let year = myDate.getFullYear();
let mouth = this.addZero(myDate.getMonth());
let day = this.addZero(myDate.getDate());
let hour = this.addZero(myDate.getHours());
let minute = this.addZero(myDate.getMinutes());
let second = this.addZero(myDate.getSeconds());
return year + '-' + String((parseInt(mouth)+1)) + '-' + day + 'T' + hour+ ':' + minute+ ':' + second
}
/**
* @param {Object} timestamp
* @param {Object} type
* 时间戳转时间
*/
timestampToTime(timestamp,type) {
if(String(timestamp).length===10) {
//时间戳为10位需*1000
var date = new Date(timestamp * 1000);
}else {
var date = new Date(timestamp);
}
var Y = date.getFullYear() + '-';
var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
var D = date.getDate() + ' ';
var h = date.getHours() + ':';
var m = date.getMinutes() + ':';
var s = date.getSeconds();
if(type==="date") {
return Y+M+D;
}else {
return Y+M+D+h+m+s;
}
}
/**
* @param {Object} time
* 时间转时间戳
*/
timeToTimestamp(time) {
//精确到秒,毫秒用000代替 :Date.parse(date);
return new Date(time).getTime();
}
/**
* @param {Object} startTime
* @param {Object} endTime
* 日期计算
*/
calculateTime(startTime,endTime) {
return new Date(startTime) - new Date(endTime)
}
/**
* @param {Object} time
* 日期转星期
*/
getDateToWeek(time) {
let weekArrayList = [
{"weekID":7,"weekName":"星期日"},
{"weekID":1,"weekName":"星期一"},
{"weekID":2,"weekName":"星期二"},
{"weekID":3,"weekName":"星期三"},
{"weekID":4,"weekName":"星期四"},
{"weekID":5,"weekName":"星期五"},
{"weekID":6,"weekName":"星期六"}];
return weekArrayList[new Date(time).getDay()]
}
/**
* @param {Object} date
* yyyy-MM-dd HH:mm:ss转为 yyyy-MM-ddTHH:mm:ss
*/
timeFormat(date,type) {
if (type == "T")
return date.replace(" ","T")
else
return date.replace("T"," ")
}
/**
* @param {Object} time
* 定时器
*/
timeSleep(time) {
return new Promise((resolve)=>setTimeout(resolve,time))
}
}
export default new TimeMethod();
三、消息菜单组件 ChatMsgMenu.vue(会在消息功能菜单中引用,长按消息会显示该菜单),组件使用的uniapp easycome模式,直接拿来使用即可
<template>
<view class="menu-box">
<view v-for="item,index in menuList" :key="item.id">
<view class="menu-box-inner" v-if="msgUserId==userId || !item.isShowSelf" @click="clickMenu(item.type,content)">
<image class="menu-icon" :src="item.icon" />
<view class="text-style">{{ item.name }}view>
view>
view>
view>
template>
<script>
export default {
props: {
msgUserId: {
type: String,
default: ""
},
msgId: {
type: Number,
default: 0
},
msgSortId: {
type: Number,
default: 0
},
content: {
type: String,
default: ""
}
,time: {
type: String,
default: ""
}
},
data() {
return {
menuList: [
{"id":1,"isShowSelf":true,"name":"撤回","type":"cancel","icon":"http://xxx/static/app/logo/publicLogo/cancel.png"},
{"id":2,"isShowSelf":false,"name":"复制","type":"copy","icon":"http://xxx/static/app/logo/publicLogo/copy.png"},
{"id":3,"isShowSelf":false,"name":"引用","type":"quote","icon":"http://xxx/static/app/logo/publicLogo/quote.png"}
],
userId: uni.getStorageSync("userId")
}
},
methods: {
//点击菜单
clickMenu(type,text) {
switch(type) {
case "cancel":
this.delMsg();
break;
case "copy":
this.copyText(text)
break;
default:
console.log(type)
}
},
//撤回消息
delMsg() {
this.$request("/msg/delMsg","DELETE",{"msgId":this.msgId},{"Content-Type":"application/x-www-form-urlencoded"}).then(res=>{
if (res.data.status=="ok") {
uni.showToast({
icon: "none",
title: "消息已撤回",
duration: 1000
})
this.$emit("cancelMsg",this.msgSortId);
}
})
},
//复制信息
copyText(text) {
wx.setClipboardData({
data: text,
success(res) {
wx.getClipboardData({
success(res) {
uni.showToast({
icon: 'none',
title: "复制成功",
duration: 1000
})
}
})
}
})
}
}
}
script>
<style lang="scss">
center {
display: flex;
align-items: center;
justify-content: center;
}
.menu-box {
min-width: 100rpx;
height: 100rpx;
@extend center;
.menu-box-inner {
width: 90rpx;
@extend center;
flex-direction: column;
.menu-icon {
width: 45rpx;
height: 45rpx;
}
.text-style {
font-size: 23rpx;
color: #e6e6e6;
}
}
}
style>