撤回的设计
通过聊天,发送一个信息,界面自动将信息撤回,
当时要有时间的限制。同时也要将撤回记录到数据库中。
async sendMessage(message, type = 'text') {
this.$refs.popup.close();
const messageData = {
sn: uuidv4(),
group_name: this.groupName,
avatar: this.user.avatar_url,
content: message,
user_name: this.user.username,
type: type,
fid: this.user.id,
tid: this.tid,
created_at: this.getCurrentTimeToMinute(),
receiver_type: this.receiver_type
};
this.socket.emit('sendMessage', messageData);
this.inputValue = '';
const token = uni.getStorageSync('token');
try {
const [error, response] = await uni.request({
url: `${config.apiBaseUrl}/addmessage`,
method: 'POST',
header: {
Authorization: `Bearer ${token}`
},
data: messageData
});
if (error) {
throw new Error(`Request failed with error: ${error}`);
}
} catch (error) {}
this.$nextTick(() => {
this.setScrollTop();
});
},
以上是发送信息到websocket, 又将信息写入到数据库。我们用了一个sn,让记录保执唯一,以利于我们撤消。所以也要要求我们收到的信息也有这一个sn.
this.socket.on('message', (msg) => {
if (msg.type == 'broadcast') {
return;
}
let msgs = {
sn:msg.sn,
avatar: msg.avatar,
isMe: msg.fid == this.user.id ? true : false,
content: msg.content,
type: msg.type,
sn: msg.sn,
createat: Math.floor(Date.now() / 1000),
time: Date.now(),
withdraw:0,
};
this.list.push(msgs);
this.setScrollTop();
});
注意这时我们加了一个time 为的是进行撤消判断。我们假设5分钟内可以撤消。withdraw表是撤消否,表示没有撤。重写以上代码:
this.socket.on('message', (msg) => {
if (msg.type == 'broadcast') {
return;
}
if (msg.type == 'widthdraw') {
//查出 msg.sn 将此记录信息改为撤回
this.list.forEach((item, index) => {
if (item.sn == msg.content) {
this.list[index].content = '[消息已撤回]';
this.list[index].type = 'text';
this.list[index].withdraw = 1;
}
});
return;
}
let msgs = {
sn:msg.sn,
avatar: msg.avatar,
isMe: msg.fid == this.user.id ? true : false,
content: msg.content,
type: msg.type,
sn: msg.sn,
createat: Math.floor(Date.now() / 1000),
time: Date.now(),
withdraw:0,
};
this.list.push(msgs);
this.setScrollTop();
});
},
当然搪消时,我们要写一个方向法:
withdraw(item) {
let _=this;
const currentTime = Date.now();
const messageTime = parseInt(item.time);
const oneMinute = config.minute; // 60 * 1000 milliseconds
if (currentTime <( messageTime + oneMinute)) {
uni.showModal({
title: '提示',
content: '确认删除该条信息吗?',
success: function (res) {
if (res.confirm) {
// 执行确认后的操作
if(_.canwithdraw(item)){
const messageData = {
sn: uuidv4(),
group_name: _.groupName,
avatar: _.user.avatar_url,
content: item.sn,
user_name: _.user.username,
type: 'widthdraw',
fid: _.user.id,
tid: _.tid,
created_at: _.getCurrentTimeToMinute(),
receiver_type: _.receiver_type
};
_.socket.emit('sendMessage', messageData);
}else{
uni.showToast({
title: '超过一分钟不能撤回',
icon: 'none'
});
}
} else {
// 执行取消后的操作
}
}
});
}
},
canwithdraw(item){
const currentTime = Date.now();
const messageTime = parseInt(item.time);
const oneMinute = config.minute; // 60 * 1000 milliseconds
if (currentTime > (messageTime + oneMinute)) {
return false;
}else{
return true;
}
},
canwithdraw是判断是不是可以取消聊天。这里我们改一下聊天窗口,将撤消加进去:
<template>
<view class="">
<scroll-view
scroll-y="true"
class="scroll-box"
:style="{ height: windowObj.windowHeight - windowObj.statusBarHeight - 94 + 'px' }"
:scroll-top="scrollHeight"
@scrolltoupper="loadMores"
>
<view class="scroll-view">
<view class="news-box" v-for="(item, index) in list" :key="index">
<view class="message-type" v-if="['left', 'join', 'kick'].includes(item.type)">
{{ item.content }}
</view>
<image
class="avatar"
:class="[item.isMe ? 'is-me' : 'avatar-right']"
:src="item.avatar"
mode="aspectFill"
v-if="item.type != 'kick' && item.type != 'join' && item.type != 'left'"
></image>
<view class="message-box" :class="{ 'is-me': item.isMe }" v-if="item.type != 'kick' && item.type != 'join' && item.type != 'left'">
<text class="message" v-if="item.type == 'text'">
{{ item.content || '' }}
<image src="../../static/withdraw.png" style="width: 50rpx; height: 50rpx" mode="aspectFill" v-if="item.isMe&&canwithdraw(item)&&item.withdraw==0" @tap="withdraw(item)"></image>
</text>
<text class="message_img" v-if="item.type == 'image'">
<image class="message-image" :src="item.content" mode="aspectFill" @click="playVoice(item.content)" />
<image src="../../static/withdraw.png" style="width: 50rpx; height: 50rpx" mode="aspectFill" v-if="item.isMe&&canwithdraw(item)&&item.withdraw==0" @tap="withdraw(item)"></image>
</text>
<text class="message_img" v-if="item.type == 'video'">
<video v-if="item.content" :src="item.content" controls></video>
<image src="../../static/withdraw.png" style="width: 50rpx; height: 50rpx" mode="aspectFill" v-if="item.isMe&&canwithdraw(item)&&item.withdraw==0" @tap="withdraw(item)"></image>
</text>
<text class="message_img" v-if="item.type == 'audio'">
<image class="message-image" src="../../static/chat/play.png" mode="aspectFill" />
<image src="../../static/withdraw.png" style="width: 50rpx; height: 50rpx" mode="aspectFill" v-if="item.isMe&&canwithdraw(item)&&item.withdraw==0" @tap="withdraw(item)"></image>
</text>
</view>
</view>
</view>
</scroll-view>
<view class="base-btn" :class="{ 'base-btn-popup-open': isPopupOpen || isPopupAudioOpen }">
<view class="base-con unify-flex">
<view @tap="more">
<image src="../../static/chat/more.png" style="width: 50rpx; height: 50rpx"></image>
</view>
<input class="input-text" type="text" :value="inputValue" placeholder="说些什么吧" @input="getInput" @confirm="tapTo(2)" />
<view @click="tapTo(2)"><image src="../../static/chat/chat.png" style="width: 50rpx; height: 50rpx"></image></view>
</view>
</view>
<uni-popup ref="popup" type="bottom" :style="{ height: '200rpx' }" @change="onPopupChange">
<view :style="{ width: '100%', backgroundColor: '#fff', height: '200rpx', overflowY: 'scroll' }" class="popup-content">
<view class="popup-items">
<view class="popup-item" v-if="type == 'group'" @tap="adduserTogroup">
<image src="../../static/chat/add.png" style="width: 50rpx; height: 50rpx"></image>
<text>添加</text>
</view>
<view class="popup-item" @click="chooseFile">
<image src="../../static/chat/pic.png" style="width: 50rpx; height: 50rpx"></image>
<text>图片</text>
</view>
<view class="popup-item" @tap="audio">
<image src="../../static/chat/audio.png" style="width: 50rpx; height: 50rpx"></image>
<text>音频</text>
</view>
<view class="popup-item" @tap="openCamera">
<image src="../../static/chat/video.png" style="width: 50rpx; height: 50rpx"></image>
<text>视频</text>
</view>
<view class="popup-item">
<image src="../../static/chat/black.png" style="width: 50rpx; height: 50rpx"></image>
<text>拉黑</text>
</view>
<view class="popup-item" v-if="type == 'group'">
<image src="../../static/chat/exit-group.png" style="width: 50rpx; height: 50rpx"></image>
<text>退群</text>
</view>
</view>
</view>
</uni-popup>
<uni-popup ref="popupAudio" type="bottom" :style="{ height: '200rpx' }" @change="onPopupAudioChange">
<view :style="{ width: '100%', backgroundColor: '#fff', height: '200rpx', overflowY: 'scroll' }" class="popup-content">
<view class="popup-item" @click="startRecording">
<image src="../../static/chat/beginaudio.png" style="width: 50rpx; height: 50rpx"></image>
<text>录音</text>
</view>
<view class="popup-item" @click="stopRecording">
<image src="../../static/chat/stop.png" style="width: 50rpx; height: 50rpx"></image>
<text>停止</text>
</view>
<view class="popup-item" @tap="playRecording">
<image src="../../static/chat/play.png" style="width: 50rpx; height: 50rpx"></image>
<text>播放</text>
</view>
<view class="popup-item" @tap="upsong">
<image src="../../static/chat/send.png" style="width: 50rpx; height: 50rpx"></image>
<text>发送</text>
</view>
<view class="popup-item" @tap="exitchat">
<image src="../../static/chat/exit.png" style="width: 50rpx; height: 50rpx"></image>
<text>退出</text>
</view>
</view>
</uni-popup>
</view>
</template>
下面我们还要写一个接口,将撤消记录下来:
app.get('/withdraw', authenticateToken, async (req, res) => {
try {
const userId = req.user.id;
let { sn } = req.query;
await Message.update({ is_retracted:1,retracted_at:Date.now() }, { where: { sn:sn } });
return res.json({ code:0, message: '撤消成功' });
} catch (error) {
return res.json({ code:1, message: '获取用户信息时出错' });
}
});
接口这里记录。
if (msg.type == 'widthdraw') {
//查出 msg.sn 将此记录信息改为撤回
console.log(msg);
this.list.forEach((item, index) => {
if (item.sn == msg.content) {
this.list[index].content = '[消息已撤回]';
this.list[index].type = 'text';
this.list[index].withdraw = 1;
this.widthdrawRow(item.sn)
}
});
return;
}
async widthdrawRow(sn) {
const token = uni.getStorageSync('token');
if (!token) return;
try {
const [error, response] = await uni.request({
url: `${config.apiBaseUrl}/withdraw`,
method: 'GET',
header: {
Authorization: `Bearer ${token}`
},
data: {
sn: sn
}
});
if (error) {
throw new Error(`Request failed with error: ${error}`);
}
if (response.data.code === 0) {
return true;
} else {
return false;
}
} catch (error) {
return false;
}
},