2026-05-28 17:08:34 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<a-spin :spinning="loading">
|
|
|
|
|
|
<a-list item-layout="horizontal" :data-source="unreadList" :locale="locale">
|
|
|
|
|
|
<template #renderItem="{ item }">
|
|
|
|
|
|
<a-list-item class="im-chat-msg-item" @click="handleOpenChat(item)">
|
|
|
|
|
|
<template #actions>
|
|
|
|
|
|
<span class="im-chat-msg-time">{{ formatTime(item.lastTime) }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<a-list-item-meta>
|
|
|
|
|
|
<template #title>
|
|
|
|
|
|
<div class="im-chat-msg-title">
|
2026-05-28 18:46:27 +08:00
|
|
|
|
<span class="im-chat-msg-name">{{ item.displayName }}</span>
|
2026-05-28 17:08:34 +08:00
|
|
|
|
<a-badge :count="item.unreadCount" :overflow-count="99" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #description>
|
|
|
|
|
|
<div class="im-chat-msg-preview">{{ formatPreview(item.lastContent) }}</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #avatar>
|
|
|
|
|
|
<a-badge dot :offset="[-2, 2]">
|
2026-05-28 18:46:27 +08:00
|
|
|
|
<!--update-begin---author:xsl ---date:20260528 for:【IM聊天】群聊显示群图标,单聊显示头像------------->
|
|
|
|
|
|
<a-avatar v-if="item.type === 'group'" :style="{ backgroundColor: '#1677ff' }">
|
|
|
|
|
|
<Icon icon="ant-design:team-outlined" />
|
2026-05-28 17:08:34 +08:00
|
|
|
|
</a-avatar>
|
2026-05-28 18:46:27 +08:00
|
|
|
|
<a-avatar v-else :src="getAvatarUrl(item.avatar)">
|
|
|
|
|
|
{{ (item.displayName || '?').slice(0, 1) }}
|
|
|
|
|
|
</a-avatar>
|
|
|
|
|
|
<!--update-end---author:xsl ---date:20260528 for:【IM聊天】群聊显示群图标,单聊显示头像----------->
|
2026-05-28 17:08:34 +08:00
|
|
|
|
</a-badge>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-list-item-meta>
|
|
|
|
|
|
</a-list-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-list>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
|
|
|
|
|
|
<ImChatModal @register="registerImChatModal" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
|
import { computed, ref } from 'vue';
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
import { useModal } from '/@/components/Modal';
|
|
|
|
|
|
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
2026-05-28 18:46:27 +08:00
|
|
|
|
import { fetchDeptMembers, fetchGroups } from '/@/views/system/im/im.api';
|
2026-05-28 17:08:34 +08:00
|
|
|
|
import { formatImMessagePreview } from '/@/views/system/im/imMessageUtil';
|
|
|
|
|
|
import { syncImUnreadFromMembers } from '/@/views/system/im/useImUnread';
|
2026-05-28 18:46:27 +08:00
|
|
|
|
import { getCachedGroupUnreadItems, initGroupUnreadFromList } from '/@/views/system/im/imCache';
|
2026-05-28 17:08:34 +08:00
|
|
|
|
import ImChatModal from '/@/views/system/im/ImChatModal.vue';
|
|
|
|
|
|
import { openImChat } from '/@/views/system/im/imSession';
|
2026-05-28 18:46:27 +08:00
|
|
|
|
import { Icon } from '/@/components/Icon';
|
2026-05-28 17:08:34 +08:00
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'SysImChatMessageList' });
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(e: 'closeModal'): void;
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
2026-05-28 18:46:27 +08:00
|
|
|
|
//update-begin---author:xsl ---date:20260528 for:【IM聊天】统一单聊与群聊展示类型-----------
|
|
|
|
|
|
type ItemType = 'single' | 'group';
|
|
|
|
|
|
|
|
|
|
|
|
interface UnreadItem {
|
|
|
|
|
|
type: ItemType;
|
|
|
|
|
|
/** 单聊:userId;群聊:conversationId */
|
2026-05-28 17:08:34 +08:00
|
|
|
|
id: string;
|
2026-05-28 18:46:27 +08:00
|
|
|
|
displayName: string;
|
2026-05-28 17:08:34 +08:00
|
|
|
|
avatar?: string;
|
|
|
|
|
|
conversationId?: string;
|
|
|
|
|
|
lastContent?: string;
|
|
|
|
|
|
lastTime?: string;
|
|
|
|
|
|
unreadCount?: number;
|
|
|
|
|
|
}
|
2026-05-28 18:46:27 +08:00
|
|
|
|
//update-end---author:xsl ---date:20260528 for:【IM聊天】统一单聊与群聊展示类型-----------
|
2026-05-28 17:08:34 +08:00
|
|
|
|
|
|
|
|
|
|
const loading = ref(false);
|
2026-05-28 18:46:27 +08:00
|
|
|
|
const members = ref<any[]>([]);
|
|
|
|
|
|
const groups = ref<any[]>([]);
|
2026-05-28 17:08:34 +08:00
|
|
|
|
const [registerImChatModal, { openModal: openImChatModal }] = useModal();
|
|
|
|
|
|
|
2026-05-28 18:46:27 +08:00
|
|
|
|
//update-begin---author:xsl ---date:20260528 for:【IM聊天】合并单聊和群聊未读列表-----------
|
|
|
|
|
|
const unreadList = computed<UnreadItem[]>(() => {
|
|
|
|
|
|
const singleItems: UnreadItem[] = members.value
|
|
|
|
|
|
.filter((item) => (item.unreadCount || 0) > 0)
|
|
|
|
|
|
.map((item) => ({
|
|
|
|
|
|
type: 'single',
|
|
|
|
|
|
id: item.id,
|
|
|
|
|
|
displayName: item.realname || item.username || '',
|
|
|
|
|
|
avatar: item.avatar,
|
|
|
|
|
|
conversationId: item.conversationId,
|
|
|
|
|
|
lastContent: item.lastContent,
|
|
|
|
|
|
lastTime: item.lastTime,
|
|
|
|
|
|
unreadCount: item.unreadCount,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const groupItems: UnreadItem[] = groups.value
|
2026-05-28 17:08:34 +08:00
|
|
|
|
.filter((item) => (item.unreadCount || 0) > 0)
|
2026-05-28 18:46:27 +08:00
|
|
|
|
.map((item) => ({
|
|
|
|
|
|
type: 'group',
|
|
|
|
|
|
id: item.conversationId,
|
|
|
|
|
|
displayName: item.groupName || '群聊',
|
|
|
|
|
|
conversationId: item.conversationId,
|
|
|
|
|
|
lastContent: item.lastContent,
|
|
|
|
|
|
lastTime: item.lastTime,
|
|
|
|
|
|
unreadCount: item.unreadCount,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
return [...singleItems, ...groupItems].sort(
|
|
|
|
|
|
(a, b) => dayjs(b.lastTime || 0).valueOf() - dayjs(a.lastTime || 0).valueOf(),
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
//update-end---author:xsl ---date:20260528 for:【IM聊天】合并单聊和群聊未读列表-----------
|
2026-05-28 17:08:34 +08:00
|
|
|
|
|
|
|
|
|
|
const locale = computed(() => ({
|
|
|
|
|
|
emptyText: loading.value ? ' ' : '暂无未读聊天消息',
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
function getAvatarUrl(avatar?: string) {
|
|
|
|
|
|
return avatar ? getFileAccessHttpUrl(avatar) : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatPreview(content?: string) {
|
|
|
|
|
|
return formatImMessagePreview(content) || '发来一条新消息';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatTime(value?: string) {
|
|
|
|
|
|
if (!value) {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
const d = dayjs(value);
|
|
|
|
|
|
if (!d.isValid()) {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (d.isSame(dayjs(), 'day')) {
|
|
|
|
|
|
return d.format('HH:mm');
|
|
|
|
|
|
}
|
|
|
|
|
|
return d.format('MM-DD HH:mm');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 18:46:27 +08:00
|
|
|
|
//update-begin---author:xsl ---date:20260528 for:【IM聊天】同时拉取单聊成员和群聊列表,修复 syncImUnreadFromMembers 丢失群聊未读-----------
|
2026-05-28 17:08:34 +08:00
|
|
|
|
async function reload(silent = false) {
|
2026-05-28 18:46:27 +08:00
|
|
|
|
const showLoading = !silent && members.value.length === 0 && groups.value.length === 0;
|
2026-05-28 17:08:34 +08:00
|
|
|
|
if (showLoading) {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
2026-05-28 18:46:27 +08:00
|
|
|
|
const [fetchedMembers, fetchedGroups] = await Promise.all([
|
|
|
|
|
|
fetchDeptMembers().catch(() => []),
|
|
|
|
|
|
fetchGroups().catch(() => []),
|
|
|
|
|
|
]);
|
|
|
|
|
|
members.value = (fetchedMembers || []) as any[];
|
|
|
|
|
|
groups.value = (fetchedGroups || []) as any[];
|
|
|
|
|
|
// 同步群聊未读到全局缓存
|
|
|
|
|
|
initGroupUnreadFromList(groups.value);
|
|
|
|
|
|
// 合并单聊 + 群聊未读数,防止 syncImUnreadFromMembers 只传成员数据导致群聊角标清零
|
|
|
|
|
|
syncImUnreadFromMembers([...members.value, ...getCachedGroupUnreadItems()]);
|
2026-05-28 17:08:34 +08:00
|
|
|
|
} catch {
|
|
|
|
|
|
if (!silent) {
|
|
|
|
|
|
members.value = [];
|
2026-05-28 18:46:27 +08:00
|
|
|
|
groups.value = [];
|
2026-05-28 17:08:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (showLoading) {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-28 18:46:27 +08:00
|
|
|
|
//update-end---author:xsl ---date:20260528 for:【IM聊天】同时拉取单聊成员和群聊列表,修复 syncImUnreadFromMembers 丢失群聊未读-----------
|
|
|
|
|
|
|
|
|
|
|
|
//update-begin---author:xsl ---date:20260528 for:【IM聊天】点击群聊条目直接打开群聊会话-----------
|
|
|
|
|
|
async function handleOpenChat(item: UnreadItem) {
|
|
|
|
|
|
if (item.type === 'group') {
|
|
|
|
|
|
const mode = await openImChat({ conversationId: item.conversationId });
|
|
|
|
|
|
if (mode === 'modal') {
|
|
|
|
|
|
openImChatModal(true, { conversationId: item.conversationId });
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const mode = await openImChat({ targetUserId: item.id, pageContext: null });
|
|
|
|
|
|
if (mode === 'modal') {
|
|
|
|
|
|
openImChatModal(true, { targetUserId: item.id, pageContext: null });
|
|
|
|
|
|
}
|
2026-05-28 17:08:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
emit('closeModal');
|
|
|
|
|
|
}
|
2026-05-28 18:46:27 +08:00
|
|
|
|
//update-end---author:xsl ---date:20260528 for:【IM聊天】点击群聊条目直接打开群聊会话-----------
|
2026-05-28 17:08:34 +08:00
|
|
|
|
|
|
|
|
|
|
defineExpose({ reload });
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
.im-chat-msg-item {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background-color 0.15s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.im-chat-msg-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.im-chat-msg-name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: rgba(0, 0, 0, 0.88);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.im-chat-msg-preview {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
max-width: 520px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.im-chat-msg-time {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|