<template>
<div class="msg-all">
<div class="message-box" ref="msgBox">
<h4 style="height:20px">{{ name }}</h4>
<div class="message-list" ref="messageContainer" @click="handleClickInside" id="topDiv"
:style="{ height: topDivHeight }">
<div v-for="message in currentMessages" :key="message.id" :class="{ 'message': true, 'sent': message.sent }">
<div class="message-info">{{ message.name }}</div>
<div class="message-bubble" @contextmenu.prevent="$event.preventDefault(); showContextMenu($event, message)">
{{message.content }}
</div>
</div>
<div v-if="contextMenu.visible" class="context-menu"
:style="{ top: contextMenu.top + 'px', left: contextMenu.left + 'px' }">
<div class="context-menu-item" @click="deleteMessage">删除</div>
<div class="context-menu-item" @click="deleteMessage" v-if="withinTwoMinutes()">撤回</div>
</div>
</div>
<div class="resize" id="midResizeDiv" @mousedown.stop="startResize"></div>
<div class="input-area" id="bottomDiv" :style="{ height: bottomDivHeight }">
<div>
<el-popover placement="top" :width="401" trigger="click" v-model:visible="popoverVisible">
<template #reference>
<el-icon style="font-size:20px;margin:5px;" title="表情">
<PictureRounded />
</el-icon>
</template>
<div>
<div v-for="(item, index) in emojis" :key="index" @click="onEmojiSelect(item)" class="emoji">
{{ item }}
</div>
</div>
</el-popover>
<el-icon style="font-size:20px;margin:5px;" @click="isHistory = !isHistory" title="历史记录">
<Clock />
</el-icon>
<el-icon style="font-size:20px;margin:5px;" @click="handlerecovery" title="恢复">
<Refresh />
</el-icon>
</div>
<el-input v-model="newMessage" type="textarea" @keyup.enter.prevent="sendMessage" autofocus
ref="messageInput" />
<button @click="sendMessage">发送</button>
</div>
</div>
<div class="msg-History" v-if="isHistory">
<div class="datepicker">
<el-date-picker v-model="selectedDate" type="date" :editable="false" :clearable="false"
:disabledDate="disabledDate" @change="scrollToMessage"/>
</div>
<div style="height: calc(100vh - 96px);overflow: auto;" ref="messageHistory" @scroll="handleScroll">
<div v-for="message in currenthistoryMessages" :key="message.id" :id="'message'+'-'+ message.id" :class="{ 'message': true, }" style="text-align:left">
<div class="message-info">
<span>{{ message.name }}</span>
<span style="margin-left: 8px;">{{timestampToTime(message.id)}}</span>
</div>
<div class="message-bubble">
{{message.content }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import notify from "devextreme/ui/notify";
import Bus from '@/utils/bus.ts';
import mqtt from 'mqtt';
import { mqttMessageCache, queryMessagebyScore, deleteMessage,recoverMessage } from "@/api/message.js"
import { emojis } from "./expression.js"
var client
const options = {
port: 8000,
connectTimeout: 4000,
clientId: "mqtt_" + Math.random().toString(16).substr(2, 8),
username: "admin-user",
password: "admin-password",
}
client = mqtt.connect('ws://192.168.11.136/mqtt', options)
export default {
components: {
},
data() {
return {
selectedDate: new Date(),
topDivHeight: '77%',
bottomDivHeight: '20%',
minPercentageHeight: 30,
isResizing: false,
popoverVisible: false,
emojis: emojis,
messagesMap: {},
newMessage: '',
messageForm: {},
name: "",
topicValue: "",
subscribed: false,
topic: "",
uniqueIds: [],
accep_id: "",
FocuUserInfo: {},
msgObj: {},
contextMenu: {
visible: false,
top: 0,
left: 0,
},
itemObj: {},
isHistory: false,
timestamps: [],
messagesClone: {}
};
},
watch: {
isHistory(val) {
if (val == true) {
this.$nextTick(() => {
const messageContainer = this.$refs.messageHistory;
if (messageContainer) {
messageContainer.scrollTop = messageContainer.scrollHeight;
}
});
}
}
},
created() {
this.initMqtt()
this.topic = sessionStorage.getItem('topic') || '';
this.FocuUserInfo = JSON.parse(sessionStorage.getItem('FocuUserInfo')) || {};
Bus.on("msgList", e => {
const arr = [];
if (e.groupSession) {
arr.push(...e.groupSession);
}
if (e.personSession) {
arr.push(...e.personSession);
}
if (e.discussion_session) {
arr.push(...e.discussion_session);
}
this.uniqueIds = Array.from(new Set(arr.map(item => item._id)));
})
Bus.on("itemData", e => {
this.initMqtt()
this.itemObj = e
this.topic = sessionStorage.getItem('topic') || '';
this.FocuUserInfo = JSON.parse(sessionStorage.getItem('FocuUserInfo')) || {};
this.name = e._name
this.topicValue = e.node_id_session
if (!this.messagesMap.hasOwnProperty(e._id)) {
this.messagesMap[e._id] = [];
}
this.accep_id = e._shortcut ? e._shortcut : e._id
let params = {}
if (e.isGroup) {
params = {
"sendKey": this.accep_id,
"receiverKey": this.accep_id
}
} else {
const id =this.FocuUserInfo._shortcut?this.FocuUserInfo._shortcut:this.FocuUserInfo._id
params = {
"sendKey": id + "_" + this.accep_id,
"receiverKey": this.accep_id + "_" + id
}
}
this.queryMessagebyScore(params)
})
},
mounted() {
document.addEventListener('click', this.handleClickOutside);
},
computed: {
currentMessages() {
return this.messagesMap[this.accep_id] || [];
},
currenthistoryMessages() {
return this.messagesClone[this.accep_id] || [];
}
},
methods: {
handlerecovery() {
const id =this.FocuUserInfo._shortcut?this.FocuUserInfo._shortcut:this.FocuUserInfo._id
let params = {
key:id + "_" + this.accep_id
}
recoverMessage(params).then(res => {
if (this.itemObj.isGroup) {
params = {
"sendKey": this.accep_id,
"receiverKey": this.accep_id
}
} else {
const id =this.FocuUserInfo._shortcut?this.FocuUserInfo._shortcut:this.FocuUserInfo._id
params = {
"sendKey": id + "_" + this.accep_id,
"receiverKey": this.accep_id + "_" + id
}
}
this.queryMessagebyScore(params)
})
},
handleScroll() {
const currenthistoryMessages = this.$refs.currenthistoryMessages;
const todayDate = new Date().toDateString();
let currentDate = todayDate;
let visibleMessageDate = null;
for (let i = 0; i < this.currenthistoryMessages.length; i++) {
const messageElement =document.getElementById(`message-${this.currenthistoryMessages[i].id}`)
if (this.isElementInViewport(messageElement)) {
this.selectedDate=Number(this.currenthistoryMessages[i].id);
break;
}
}
},
isElementInViewport(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
},
scrollToMessage() {
const selectedDate = this.getNewTime(this.selectedDate);
const messages = this.currenthistoryMessages;
for (let i = 0; i < messages.length; i++) {
const messageDate = this.getNewTime(messages[i].id);
if (selectedDate == messageDate) {
const messageElement = document.getElementById(`message-${messages[i].id}`);
if (messageElement) {
messageElement.scrollIntoView({ behavior: "auto", block: "nearest" });
break;
}
}
}
},
disabledDate(date) {
const timestamp = date.getTime();
return !this.timestamps.includes(timestamp);
},
startResize(event) {
this.isResizing = true;
const startY = event.clientY;
const startTopHeight = parseFloat(this.topDivHeight);
const startBottomHeight = parseFloat(this.bottomDivHeight);
const containerHeight = this.$refs.msgBox.clientHeight;
console.log(containerHeight)
const handleMouseMove = (moveEvent) => {
if (!this.isResizing) return;
const deltaY = (moveEvent.clientY - startY) / (containerHeight) * 100;
let newTopHeight = startTopHeight + deltaY;
let newBottomHeight = startBottomHeight - deltaY;
console.log(newTopHeight)
if (newTopHeight < this.minPercentageHeight) {
newTopHeight = this.minPercentageHeight;
newBottomHeight = 100 - this.minPercentageHeight;
} else if (newBottomHeight < this.minPercentageHeight) {
newTopHeight = 100 - this.minPercentageHeight;
newBottomHeight = this.minPercentageHeight;
}
this.topDivHeight = newTopHeight + '%';
this.bottomDivHeight = newBottomHeight + '%';
};
const handleMouseUp = () => {
this.isResizing = false;
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
},
withinTwoMinutes() {
const currentTimestamp = new Date().getTime();
const twoMinutesInMilliseconds = 2 * 60 * 1000;
const timeDifference = Math.abs(currentTimestamp - this.msgObj.id);
return timeDifference <= twoMinutesInMilliseconds;
},
deleteMessage() {
const id =this.FocuUserInfo._shortcut?this.FocuUserInfo._shortcut:this.FocuUserInfo._id
let params = {
time: this.msgObj.id,
key: this.msgObj.sent == false ? this.accep_id + "_" + id : id + "_" + this.accep_id
}
deleteMessage(params).then(res => {
if (res.data.code == 200) {
const index = this.messagesMap[this.accep_id].findIndex(item => item.id === this.msgObj.id);
if (index !== -1) {
this.messagesMap[this.accep_id].splice(index, 1);
}
}
})
},
showContextMenu(event, message) {
event.preventDefault();
this.contextMenu.visible = true;
this.contextMenu.top = event.clientY -
20;
this.contextMenu.left = event.layerX;
this.msgObj = message
},
onEmojiSelect(emoji) {
this.newMessage += emoji
this.popoverVisible = false
},
initMqtt() {
client.on('connect', (e) => {
if (e.returnCode == 0) {
setTimeout(() => {
if (!this.subscribed) {
const ids = this.FocuUserInfo?.discussion_session?.map(session => session.id) || [];
ids.push(this.topic)
client.subscribe(ids, { qos: 0 }, (error) => {
if (error) {
console.error('订阅失败:', error);
} else {
console.log('订阅成功');
this.subscribed = true;
}
})
}
}, 1000)
} else {
this.reconnect()
}
})
client.on('message', (topic, message) => {
console.log('收到来自' + topic, '的消息', message.toString())
try {
const msgObj = JSON.parse(message.toString())
console.log(msgObj)
if (!this.messagesMap.hasOwnProperty(msgObj._id)) {
this.messagesMap[msgObj._id] = [];
}
if (this.messageForm.id != msgObj.id && this.messageForm.name != msgObj.name) {
this.messagesMap[msgObj._id].push(msgObj);
}
this.$nextTick(() => {
const messageContainer = this.$refs.messageContainer;
if (messageContainer) {
messageContainer.scrollTop = messageContainer.scrollHeight;
}
});
} catch (error) {
}
})
},
reconnect() {
client.end(true, () => {
client.reconnect();
});
},
sendMessage() {
if (this.topicValue === '') {
notify("请选择发送对象", "warning", 2000, {
direction: "up-push",
});
return
} else if (!this.newMessage.trim()) {
notify("请输入发送内容", "warning", 2000, {
direction: "up-push",
});
return
}
const id =this.FocuUserInfo._shortcut?this.FocuUserInfo._shortcut:this.FocuUserInfo._id
this.messageForm = {
id: Date.now(),
content: this.newMessage,
name: this.FocuUserInfo._name,
_id: this.itemObj.isGroup ? this.itemObj._id : id,
};
client.publish(this.topicValue, JSON.stringify(this.messageForm), { qos: 0 }, (err) => {
if (err) {
console.error('Error publishing message:', err);
} else {
if (!this.messagesMap.hasOwnProperty(this.accep_id)) {
this.messagesMap[this.accep_id] = [];
}
this.messagesMap[this.accep_id].push({
id: Date.now(),
content: this.newMessage,
name: this.messageForm.name,
sent: true,
showContextMenu: false
});
if (!this.messagesClone.hasOwnProperty(this.accep_id)) {
this.messagesClone[this.accep_id] = []
}
this.messagesClone[this.accep_id].push({
id: Date.now(),
content: this.newMessage,
name: this.messageForm.name,
sent: true,
showContextMenu: false
});
let params = {
"sender": id,
"receiver": this.accep_id,
"content": this.newMessage,
"time": Date.now(),
"type": "0",
"senderName": this.FocuUserInfo._name
}
this.mqttMessageCache(params)
this.newMessage = '';
this.$refs.messageInput.focus();
this.$nextTick(() => {
const messageContainer = this.$refs.messageContainer;
messageContainer.scrollTop = messageContainer.scrollHeight;
});
}
});
},
mqttMessageCache(params) {
mqttMessageCache(params).then(res => {
console.log(res)
})
},
queryMessagebyScore(params) {
queryMessagebyScore(params).then(res => {
if (res.data.code == 200) {
this.messagesMap[this.accep_id] = []
this.messagesClone[this.accep_id] = []
let msgList = res.data.data.slice().sort((a, b) => a.date - b.date);
msgList.forEach(element => {
const newTime = this.getNewTime(element.date)
this.timestamps.push(newTime)
this.messagesMap[this.accep_id].push({
id: element.date,
content: element.content,
name: element.label,
sent: element.label == this.FocuUserInfo._name,
showContextMenu: false
});
this.messagesClone[this.accep_id].push({
id: element.date,
content: element.content,
name: element.label,
sent: element.label == this.FocuUserInfo._name,
showContextMenu: false
});
});
this.selectedDate = this.timestamps[this.timestamps.length -1]
this.$nextTick(() => {
const messageContainer = this.$refs.messageContainer;
if (messageContainer) {
messageContainer.scrollTop = messageContainer.scrollHeight;
}
});
}
})
},
getNewTime(time) {
const date = new Date(Number(time));
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const newTimestamp = new Date(year, month - 1, day).getTime();
return newTimestamp
},
handleClickOutside(event) {
if (!this.$refs.messageContainer?.contains(event.target)) {
this.contextMenu.visible = false;
}
},
handleClickInside(event) {
if (!event.target.closest('.message-bubble')) {
this.contextMenu.visible = false;
}
},
timestampToTime(timestamp) {
if(!timestamp) return
const date = new Date(Number(timestamp));
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds =date.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
},
beforeDestroy() {
document.removeEventListener('click', this.handleClickOutside);
if (client != null) {
client.end(true, null, () => {
client = null;
});
}
},
};
</script>