<script lang="ts" setup>
|
import { onMounted, reactive, ref, toRefs } from 'vue';
|
|
import { useGlobSetting, useMessage } from '@jnpf/hooks';
|
import { ScrollContainer, useContextMenu } from '@jnpf/ui';
|
import { toDateText } from '@jnpf/utils';
|
|
import { Search } from '@vben/icons';
|
|
import { Badge } from 'ant-design-vue';
|
import dayjs from 'dayjs';
|
|
import { deleteChatRecord, getIMReply, removeChatRecord } from '#/api/system/message';
|
import { useWebSocket } from '#/hooks/web/useWebSocket';
|
import { $t } from '#/locales';
|
|
import Im from './Im.vue';
|
|
defineOptions({ name: 'Reply' });
|
|
const emit = defineEmits(['toggleTwinkle']);
|
defineExpose({ addNewChat });
|
interface State {
|
listQuery: any;
|
replyList: any[];
|
activeUserId: string;
|
}
|
|
const state = reactive<State>({
|
listQuery: {
|
keyword: '',
|
},
|
activeUserId: '',
|
replyList: [],
|
});
|
const { listQuery, activeUserId, replyList } = toRefs(state);
|
const { createConfirm } = useMessage();
|
const [createContextMenu] = useContextMenu();
|
const globSetting = useGlobSetting();
|
const apiUrl = ref(globSetting.apiURL);
|
const imRef = ref<any>(null);
|
|
const { onWebSocket, sendWsMsg } = useWebSocket();
|
|
onWebSocket((data: any) => {
|
// 接收对方发送的消息
|
if (data.method == 'receiveMessage') {
|
// 判断是否当前聊天对象
|
if (state.activeUserId !== data.formUserId) {
|
updateReply(data, 1);
|
emit('toggleTwinkle', true);
|
return;
|
}
|
const messItem = {
|
userId: data.formUserId,
|
messageType: data.messageType,
|
message: data.formMessage,
|
dateTime: dayjs(data.dateTime).format('YYYY-MM-DD HH:mm:ss'),
|
};
|
imRef.value?.addItem(messItem);
|
// 更新已读
|
const msg = { method: 'UpdateReadMessage', formUserId: data.formUserId };
|
sendWsMsg(JSON.stringify(msg));
|
updateReply(data);
|
}
|
// 显示自己发送的消息
|
if (data.method == 'sendMessage') {
|
if (state.activeUserId !== data.toUserId) return;
|
// 添加到客户端
|
const messItem = {
|
userId: data.UserId,
|
messageType: data.messageType,
|
message: data.toMessage,
|
dateTime: dayjs(data.dateTime).format('YYYY-MM-DD HH:mm:ss'),
|
};
|
updateLatestMessage(data);
|
imRef.value?.addItem(messItem);
|
}
|
});
|
|
function toggleTwinkle() {
|
const boo = state.replyList.some((o) => o.unreadMessage);
|
emit('toggleTwinkle', boo);
|
}
|
function updateReply(data, isAdd = 0) {
|
let boo = false;
|
const len = state.replyList.length;
|
for (let i = 0; i < len; i++) {
|
if (data.formUserId === state.replyList[i].id) {
|
if (isAdd) state.replyList[i].unreadMessage += 1;
|
state.replyList[i].latestMessage = data.formMessage;
|
state.replyList[i].messageType = data.messageType;
|
state.replyList[i].latestDate = data.latestDate;
|
boo = true;
|
break;
|
}
|
}
|
if (boo) return;
|
if (isAdd) data.unreadMessage = 1;
|
data.latestMessage = data.formMessage;
|
data.id = data.formUserId;
|
state.replyList.unshift(data);
|
}
|
function updateLatestMessage(data) {
|
let boo = false;
|
const len = state.replyList.length;
|
for (let i = 0; i < len; i++) {
|
if (data.toUserId === state.replyList[i].id) {
|
state.replyList[i].latestMessage = data.toMessage;
|
state.replyList[i].messageType = data.messageType;
|
state.replyList[i].latestDate = data.latestDate;
|
boo = true;
|
break;
|
}
|
}
|
if (boo) return;
|
const item = {
|
account: data.toAccount,
|
headIcon: data.toHeadIcon,
|
id: data.toUserId,
|
latestDate: data.latestDate,
|
latestMessage: data.toMessage,
|
messageType: data.messageType,
|
realName: data.toRealName,
|
unreadMessage: 0,
|
};
|
state.replyList.unshift(item);
|
}
|
|
function handleContext(e: MouseEvent, item) {
|
(createContextMenu as any)({
|
event: e,
|
items: [
|
{
|
label: '移除',
|
handler: () => {
|
removeRecord(item);
|
},
|
},
|
{
|
label: '删除聊天记录',
|
handler: () => {
|
delChatRecord(item);
|
},
|
},
|
],
|
});
|
}
|
function removeRecord(item) {
|
if (state.activeUserId === item.id) state.activeUserId = '';
|
removeChatRecord(item.id).then(() => {
|
state.replyList = state.replyList.filter((o) => o.id !== item.id);
|
toggleTwinkle();
|
});
|
}
|
function delChatRecord(item) {
|
createConfirm({
|
iconType: 'warning',
|
title: '提示',
|
content: `是否清空当前聊天的所有记录?`,
|
zIndex: 10000,
|
onOk: () => {
|
deleteChatRecord(item.id).then(() => {
|
item.latestMessage = '';
|
item.messageType = '';
|
item.unreadMessage = 0;
|
item.latestDate = 0;
|
if (state.activeUserId === item.id) imRef.value?.clearMsgList();
|
toggleTwinkle();
|
});
|
},
|
});
|
}
|
function getMsgText(text, type) {
|
if (type === 'voice') return '[语音]';
|
if (type === 'image') return '[图片]';
|
return text;
|
}
|
function readInfo(item) {
|
if (state.activeUserId === item.id) return;
|
state.activeUserId = item.id;
|
const msg = {
|
method: 'UpdateReadMessage',
|
formUserId: item.id,
|
};
|
sendWsMsg(JSON.stringify(msg));
|
item.unreadMessage = 0;
|
toggleTwinkle();
|
imRef.value?.getMessageList(item);
|
}
|
function getReplyList() {
|
getIMReply(state.listQuery).then((res) => {
|
state.replyList = res.data.list;
|
});
|
}
|
function addNewChat(data) {
|
if (state.activeUserId === data.id) return;
|
if (!state.replyList.some((o) => o.id === data.id)) state.replyList.unshift(data);
|
readInfo(data);
|
}
|
onMounted(() => {
|
getReplyList();
|
});
|
</script>
|
<template>
|
<div class="reply-pane common-pane">
|
<div class="chat-list">
|
<div class="chat-list-header">
|
<a-input :placeholder="$t('common.drawerSearchText')" allow-clear v-model:value="listQuery.keyword" :bordered="false" @keyup.enter="getReplyList">
|
<template #suffix>
|
<Search class="size-4" @click="getReplyList" />
|
</template>
|
</a-input>
|
</div>
|
<div class="chat-list-main">
|
<ScrollContainer class="reply-list">
|
<template v-if="replyList.length">
|
<div
|
v-for="(item, i) in replyList"
|
:key="i"
|
class="reply-list-item"
|
:class="{ 'reply-list-item__active': activeUserId === item.id }"
|
@click="readInfo(item)"
|
@contextmenu="handleContext($event, item)">
|
<div class="reply-list-item-main">
|
<a-avatar :size="36" :src="apiUrl + item.headIcon" />
|
<div class="reply-list-txt">
|
<p class="reply-list-title">
|
<span class="title-left">{{ item.realName }}/{{ item.account }}</span>
|
<Badge :count="item.unreadMessage" v-if="item.unreadMessage" />
|
</p>
|
<p class="reply-list-info">
|
<span class="reply-list-content">{{ getMsgText(item.latestMessage, item.messageType) }}</span>
|
<span class="reply-list-time">{{ toDateText(item.latestDate) }}</span>
|
</p>
|
</div>
|
</div>
|
</div>
|
</template>
|
<jnpf-empty v-if="!replyList.length" />
|
</ScrollContainer>
|
</div>
|
</div>
|
<div class="chat-content">
|
<Im ref="imRef" v-show="activeUserId" />
|
<div class="chat-content-empty" v-show="!activeUserId">
|
<jnpf-empty />
|
</div>
|
</div>
|
</div>
|
</template>
|