diff --git a/src/api/im/manager/friend/index.ts b/src/api/im/manager/friend/index.ts new file mode 100644 index 000000000..20aa673fb --- /dev/null +++ b/src/api/im/manager/friend/index.ts @@ -0,0 +1,20 @@ +import request from '@/config/axios' + +export interface ImManagerFriendVO { + id: number + userId: number + userNickname?: string + friendUserId: number + friendNickname?: string + displayName?: string + muted: boolean + status: number + addTime?: Date + deleteTime?: Date + createTime: Date +} + +// 获得好友关系分页 +export const getManagerFriendPage = (params: PageParam) => { + return request.get({ url: '/im/manager/friend/page', params }) +} diff --git a/src/api/im/manager/message/group.ts b/src/api/im/manager/message/group.ts new file mode 100644 index 000000000..1cf47380d --- /dev/null +++ b/src/api/im/manager/message/group.ts @@ -0,0 +1,28 @@ +import request from '@/config/axios' + +// TODO @AI:应该是 message/group/xxx,保持和前端一致 +export interface ImManagerGroupMessageVO { + id: number + clientMessageId?: string + groupId: number + groupName?: string + senderId: number + senderNickname?: string + type: number + content: string + status: number + atUserIds?: number[] + receiptStatus?: number + sendTime: Date + createTime: Date +} + +// 获得群聊消息分页 +export const getManagerGroupMessagePage = (params: PageParam) => { + return request.get({ url: '/im/manager/message/group/page', params }) +} + +// 获得群聊消息详情 +export const getManagerGroupMessage = (id: number) => { + return request.get({ url: '/im/manager/message/group/get?id=' + id }) +} diff --git a/src/api/im/manager/message/private.ts b/src/api/im/manager/message/private.ts new file mode 100644 index 000000000..158e0f700 --- /dev/null +++ b/src/api/im/manager/message/private.ts @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// TODO @AI:应该是 message/group/xxx,保持和前端一致 +export interface ImManagerPrivateMessageVO { + id: number + clientMessageId?: string + senderId: number + senderNickname?: string + receiverId: number + receiverNickname?: string + type: number + content: string + status: number + sendTime: Date + createTime: Date +} + +// 获得私聊消息分页 +export const getManagerPrivateMessagePage = (params: PageParam) => { + return request.get({ url: '/im/manager/message/private/page', params }) +} + +// 获得私聊消息详情 +export const getManagerPrivateMessage = (id: number) => { + return request.get({ url: '/im/manager/message/private/get?id=' + id }) +} diff --git a/src/api/im/manager/sensitiveWord/index.ts b/src/api/im/manager/sensitiveWord/index.ts new file mode 100644 index 000000000..c562b1ee7 --- /dev/null +++ b/src/api/im/manager/sensitiveWord/index.ts @@ -0,0 +1,42 @@ +import request from '@/config/axios' + +export interface ImManagerSensitiveWordVO { + id: number + word: string + status: number + creator?: string + createTime?: Date +} + +// 获得敏感词分页 +export const getManagerSensitiveWordPage = (params: PageParam) => { + return request.get({ url: '/im/manager/sensitive-word/page', params }) +} + +// 获得敏感词详情 +export const getManagerSensitiveWord = (id: number) => { + return request.get({ url: '/im/manager/sensitive-word/get?id=' + id }) +} + +// 新增敏感词 +export const createManagerSensitiveWord = (data: ImManagerSensitiveWordVO) => { + return request.post({ url: '/im/manager/sensitive-word/create', data }) +} + +// 修改敏感词 +export const updateManagerSensitiveWord = (data: ImManagerSensitiveWordVO) => { + return request.put({ url: '/im/manager/sensitive-word/update', data }) +} + +// 删除敏感词 +export const deleteManagerSensitiveWord = (id: number) => { + return request.delete({ url: '/im/manager/sensitive-word/delete?id=' + id }) +} + +// 批量删除敏感词 +export const deleteManagerSensitiveWordList = (ids: number[]) => { + return request.delete({ + url: '/im/manager/sensitive-word/delete-list', + params: { ids: ids.join(',') } + }) +} diff --git a/src/api/im/manager/statistics/index.ts b/src/api/im/manager/statistics/index.ts new file mode 100644 index 000000000..4df7fca9b --- /dev/null +++ b/src/api/im/manager/statistics/index.ts @@ -0,0 +1,122 @@ +// IM 数据看板 API +// +// vibe 阶段:所有方法用本地 mock(setTimeout + 随机数据)实现,不发真实请求。 +// 后端就绪后把 mockXxx 调用替换为 request.get(...) 一行即可。 +// +// import request from '@/config/axios' + +export interface ImStatisticsOverviewVO { + totalUser: number + newUserToday: number + totalGroup: number + newGroupToday: number + activeUserDaily: number + activeUserWeekly: number + activeUserMonthly: number + privateMessageToday: number + groupMessageToday: number + privateMessageYesterday: number + groupMessageYesterday: number +} + +export interface ImStatisticsTrendVO { + dates: string[] + series: Record +} + +export interface ImStatisticsDistributionVO { + messageTypeDistribution: { name: string; value: number }[] + groupSizeDistribution: { range: string; count: number }[] + topSenders: { userId: number; nickname: string; messageCount: number }[] +} + +// ==================== mock helpers ==================== + +const fakePromise = (data: T, delay = 300): Promise => + new Promise((r) => setTimeout(() => r(data), delay)) + +const randomInt = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min + +const buildDates = (days: number): string[] => { + const dates: string[] = [] + const today = new Date() + for (let i = days - 1; i >= 0; i--) { + const d = new Date(today) + d.setDate(d.getDate() - i) + dates.push(d.toISOString().slice(0, 10)) + } + return dates +} + +// ==================== exposed APIs ==================== + +// 获得 KPI 概览 +export const getStatisticsOverview = (): Promise => { + return fakePromise({ + totalUser: 12345, + newUserToday: 23, + totalGroup: 678, + newGroupToday: 4, + activeUserDaily: 1023, + activeUserWeekly: 4567, + activeUserMonthly: 8901, + privateMessageToday: 8765, + groupMessageToday: 3210, + privateMessageYesterday: 7890, + groupMessageYesterday: 3000 + }) + // 真实请求版本:return request.get({ url: '/im/manager/statistics/overview' }) +} + +// 获得消息趋势(私聊 + 群聊双线) +export const getMessageTrend = (days: number): Promise => { + const dates = buildDates(days) + return fakePromise({ + dates, + series: { + private: dates.map(() => randomInt(500, 2000)), + group: dates.map(() => randomInt(200, 1200)) + } + }) + // return request.get({ url: '/im/manager/statistics/message-trend', params: { days } }) +} + +// 获得用户趋势(新增注册 + 日活双线) +export const getUserTrend = (days: number): Promise => { + const dates = buildDates(days) + return fakePromise({ + dates, + series: { + register: dates.map(() => randomInt(5, 80)), + active: dates.map(() => randomInt(800, 1500)) + } + }) + // return request.get({ url: '/im/manager/statistics/user-trend', params: { days } }) +} + +// 获得分布数据(消息类型 / 群规模 / TOP 发送者) +export const getStatisticsDistribution = (): Promise => { + return fakePromise({ + messageTypeDistribution: [ + { name: '文本', value: 8000 }, + { name: '图片', value: 2400 }, + { name: '视频', value: 320 }, + { name: '语音', value: 980 }, + { name: '文件', value: 540 }, + { name: '位置', value: 65 }, + { name: '名片', value: 32 } + ], + groupSizeDistribution: [ + { range: '1-9 人', count: 320 }, + { range: '10-49 人', count: 240 }, + { range: '50-199 人', count: 95 }, + { range: '200+ 人', count: 23 } + ], + topSenders: Array.from({ length: 10 }, (_, i) => ({ + userId: 1000 + i, + nickname: `测试用户${i + 1}`, + messageCount: 1500 - i * 120 + })) + }) + // return request.get({ url: '/im/manager/statistics/distribution' }) +} diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 2b4b06986..f563a1cb2 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -768,36 +768,51 @@ const remainingRouter: AppRouteRecordRaw[] = [ meta: { hidden: true, title: '消息' } }, { - path: 'friend', - component: () => import('@/views/im/home/pages/friend/index.vue'), - name: 'ImHomeFriend', - meta: { hidden: true, title: '好友' } - }, - { - path: 'group', - component: () => import('@/views/im/home/pages/group/index.vue'), - name: 'ImHomeGroup', - meta: { hidden: true, title: '群聊' } + path: 'contact', + component: () => import('@/views/im/home/pages/contact/index.vue'), + name: 'ImHomeContact', + meta: { hidden: true, title: '通讯录' } } ] }, { - path: 'manager/message', + path: 'manager/message/private', component: Layout, - name: 'ImManagerMessage', - redirect: '/im/manager/message/index', + name: 'ImManagerPrivateMessage', + redirect: '/im/manager/message/private/index', meta: { hidden: false }, children: [ { path: 'index', - component: () => import('@/views/im/manager/message/index.vue'), - name: 'ImManagerMessageIndex', + component: () => import('@/views/im/manager/message/private/index.vue'), + name: 'ImManagerPrivateMessageIndex', meta: { canTo: true, hidden: false, noTagsView: false, - icon: 'ep:chat-dot-round', - title: '消息管理' + icon: 'ep:user', + title: '私聊消息' + } + } + ] + }, + { + path: 'manager/message/group', + component: Layout, + name: 'ImManagerGroupMessage', + redirect: '/im/manager/message/group/index', + meta: { hidden: false }, + children: [ + { + path: 'index', + component: () => import('@/views/im/manager/message/group/index.vue'), + name: 'ImManagerGroupMessageIndex', + meta: { + canTo: true, + hidden: false, + noTagsView: false, + icon: 'ep:chat-line-round', + title: '群聊消息' } } ] @@ -843,6 +858,48 @@ const remainingRouter: AppRouteRecordRaw[] = [ } } ] + }, + { + path: 'manager/sensitive-word', + component: Layout, + name: 'ImManagerSensitiveWord', + redirect: '/im/manager/sensitive-word/index', + meta: { hidden: false }, + children: [ + { + path: 'index', + component: () => import('@/views/im/manager/sensitive-word/index.vue'), + name: 'ImManagerSensitiveWordIndex', + meta: { + canTo: true, + hidden: false, + noTagsView: false, + icon: 'ep:warning', + title: '敏感词管理' + } + } + ] + }, + { + path: 'manager/statistics', + component: Layout, + name: 'ImManagerStatistics', + redirect: '/im/manager/statistics/index', + meta: { hidden: false }, + children: [ + { + path: 'index', + component: () => import('@/views/im/manager/statistics/index.vue'), + name: 'ImManagerStatisticsIndex', + meta: { + canTo: true, + hidden: false, + noTagsView: false, + icon: 'ep:trend-charts', + title: '数据看板' + } + } + ] } ] } diff --git a/src/views/im/home/components/ToolBar.vue b/src/views/im/home/components/ToolBar.vue index 4d65fcef1..9fedb85f6 100644 --- a/src/views/im/home/components/ToolBar.vue +++ b/src/views/im/home/components/ToolBar.vue @@ -55,7 +55,7 @@ import { useRoute, useRouter } from 'vue-router' import Icon from '@/components/Icon/src/Icon.vue' import { useUserStore } from '@/store/modules/user' import { useConversationStore } from '../store/conversationStore' -import UserAvatar from './UserAvatar.vue' +import UserAvatar from './user/UserAvatar.vue' defineOptions({ name: 'ImToolBar' }) @@ -68,15 +68,14 @@ const conversationStore = useConversationStore() const totalUnread = computed(() => conversationStore.getTotalUnread) /** - * 三个主 Tab 的配置,name 对应路由 ImHomeConversation/Friend/Group + * 两个主 Tab 的配置,name 对应路由 ImHomeConversation/Contact * 用 name 而非 path:path 后期容易变(前缀调整、嵌套加层),name 更稳定 * icon 走通用 组件,支持 iconify 全部前缀(ep: / ant-design: / svg-icon: 等) - * 群聊用 ant-design:team(三人组合):ep 没有"群体"图标,三人剪影跟 ep:user(单人)一眼区分单人 / 群体 + * 通讯录用 ant-design:contacts-outlined:与消息图标一眼区分;好友 + 群聊在通讯录内分组展示 */ const tabs = [ { name: 'ImHomeConversation', label: '消息', icon: 'ep:chat-dot-round' }, - { name: 'ImHomeFriend', label: '好友', icon: 'ep:user' }, - { name: 'ImHomeGroup', label: '群聊', icon: 'ant-design:team' } + { name: 'ImHomeContact', label: '通讯录', icon: 'ant-design:contacts-outlined' } ] /** 当前路由是否命中 Tab:直接比对 route.name */ diff --git a/src/views/im/home/components/UserInfoCard.vue b/src/views/im/home/components/UserInfoCard.vue deleted file mode 100644 index 92320bcc1..000000000 --- a/src/views/im/home/components/UserInfoCard.vue +++ /dev/null @@ -1,179 +0,0 @@ - - - diff --git a/src/views/im/home/components/friend/FriendItem.vue b/src/views/im/home/components/friend/FriendItem.vue new file mode 100644 index 000000000..96e7f00f6 --- /dev/null +++ b/src/views/im/home/components/friend/FriendItem.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/views/im/home/components/group/GroupItem.vue b/src/views/im/home/components/group/GroupItem.vue new file mode 100644 index 000000000..23a7403ae --- /dev/null +++ b/src/views/im/home/components/group/GroupItem.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/views/im/home/components/GroupMember.vue b/src/views/im/home/components/group/GroupMember.vue similarity index 97% rename from src/views/im/home/components/GroupMember.vue rename to src/views/im/home/components/group/GroupMember.vue index b893588b9..988147fd4 100644 --- a/src/views/im/home/components/GroupMember.vue +++ b/src/views/im/home/components/group/GroupMember.vue @@ -27,7 +27,7 @@ diff --git a/src/views/im/home/components/UserAvatar.vue b/src/views/im/home/components/user/UserAvatar.vue similarity index 95% rename from src/views/im/home/components/UserAvatar.vue rename to src/views/im/home/components/user/UserAvatar.vue index 77f750956..ed2cf0daf 100644 --- a/src/views/im/home/components/UserAvatar.vue +++ b/src/views/im/home/components/user/UserAvatar.vue @@ -44,8 +44,8 @@ + + + diff --git a/src/views/im/home/components/user/UserInfoCard.vue b/src/views/im/home/components/user/UserInfoCard.vue new file mode 100644 index 000000000..ad1ff7582 --- /dev/null +++ b/src/views/im/home/components/user/UserInfoCard.vue @@ -0,0 +1,119 @@ + + + diff --git a/src/views/im/home/index.vue b/src/views/im/home/index.vue index 85348d4e2..fa2a4fb64 100644 --- a/src/views/im/home/index.vue +++ b/src/views/im/home/index.vue @@ -35,7 +35,7 @@ import { useMessagePuller } from './composables/useMessagePuller' import { useMessageSender } from './composables/useMessageSender' import { ImConversationType } from '../utils/constants' import ToolBar from './components/ToolBar.vue' -import UserInfoCard from './components/UserInfoCard.vue' +import UserInfoCard from './components/user/UserInfoCard.vue' import ContextMenu from './components/ContextMenu.vue' defineOptions({ name: 'ImIndex' }) diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue index c54167a49..15c530b45 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue @@ -274,8 +274,8 @@ - - + import { computed, ref, watch } from 'vue' import Icon from '@/components/Icon/src/Icon.vue' -import UserAvatar from '../../../../components/UserAvatar.vue' +import UserAvatar from '../../../../components/user/UserAvatar.vue' import { useMessage } from '@/hooks/web/useMessage' import { useConversationStore } from '@/views/im/home/store/conversationStore' diff --git a/src/views/im/home/pages/conversation/components/input/MentionPicker.vue b/src/views/im/home/pages/conversation/components/input/MentionPicker.vue index fb09cd64d..3db535771 100644 --- a/src/views/im/home/pages/conversation/components/input/MentionPicker.vue +++ b/src/views/im/home/pages/conversation/components/input/MentionPicker.vue @@ -65,7 +65,7 @@ import Icon from '@/components/Icon/src/Icon.vue' import { useUserStore } from '@/store/modules/user' import { CommonStatusEnum } from '@/utils/constants' import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '@/views/im/utils/constants' -import GroupMember, { type GroupMemberLite } from '../../../../components/GroupMember.vue' +import GroupMember, { type GroupMemberLite } from '../../../../components/group/GroupMember.vue' defineOptions({ name: 'ImMentionPicker' }) diff --git a/src/views/im/home/pages/conversation/components/input/MessageInput.vue b/src/views/im/home/pages/conversation/components/input/MessageInput.vue index 6fa5fc48c..11b351fc5 100644 --- a/src/views/im/home/pages/conversation/components/input/MessageInput.vue +++ b/src/views/im/home/pages/conversation/components/input/MessageInput.vue @@ -137,7 +137,7 @@ import { import EmojiPicker from './EmojiPicker.vue' import MentionPicker from './MentionPicker.vue' import VoiceRecorder from './VoiceRecorder.vue' -import type { GroupMemberLite } from '../../../../components/GroupMember.vue' +import type { GroupMemberLite } from '../../../../components/group/GroupMember.vue' defineOptions({ name: 'ImMessageInput' }) diff --git a/src/views/im/home/pages/conversation/components/message/MessageHistory.vue b/src/views/im/home/pages/conversation/components/message/MessageHistory.vue index 08ad657fa..4a6642e7b 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageHistory.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageHistory.vue @@ -318,8 +318,8 @@ import { type AudioMessage } from '@/views/im/utils/message' import type { Message } from '@/views/im/home/types' -import UserAvatar from '../../../../components/UserAvatar.vue' -import GroupMember, { type GroupMemberLite } from '../../../../components/GroupMember.vue' +import UserAvatar from '../../../../components/user/UserAvatar.vue' +import GroupMember, { type GroupMemberLite } from '../../../../components/group/GroupMember.vue' defineOptions({ name: 'ImMessageHistory' }) diff --git a/src/views/im/home/pages/conversation/components/message/MessageItem.vue b/src/views/im/home/pages/conversation/components/message/MessageItem.vue index 6fb72a4a4..2e1b2a4f2 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue @@ -238,8 +238,8 @@ import { useImUiStore } from '../../../../store/uiStore' import { useMessageSender } from '../../../../composables/useMessageSender' import type { Message } from '../../../../types' import MessageReadStatus from './MessageReadStatus.vue' -import UserAvatar from '../../../../components/UserAvatar.vue' -import type { GroupMemberLite } from '../../../../components/GroupMember.vue' +import UserAvatar from '../../../../components/user/UserAvatar.vue' +import type { GroupMemberLite } from '../../../../components/group/GroupMember.vue' defineOptions({ name: 'ImMessageItem' }) diff --git a/src/views/im/home/pages/conversation/components/message/MessagePanel.vue b/src/views/im/home/pages/conversation/components/message/MessagePanel.vue index 17edb311a..a2b2a5207 100644 --- a/src/views/im/home/pages/conversation/components/message/MessagePanel.vue +++ b/src/views/im/home/pages/conversation/components/message/MessagePanel.vue @@ -123,9 +123,8 @@ import MessageInput from '../input/MessageInput.vue' import MessageHistory from './MessageHistory.vue' import ConversationGroupSide from '../conversation/ConversationGroupSide.vue' import ConversationPrivateSide from '../conversation/ConversationPrivateSide.vue' -import type { GroupLite } from '../../../group/components/GroupItem.vue' -import type { GroupMemberLite } from '../../../../components/GroupMember.vue' -import type { FriendLite } from '../../../friend/components/FriendItem.vue' +import type { FriendLite, GroupLite } from '../../../../types' +import type { GroupMemberLite } from '../../../../components/group/GroupMember.vue' defineOptions({ name: 'ImMessagePanel' }) diff --git a/src/views/im/home/pages/conversation/components/message/MessageReadStatus.vue b/src/views/im/home/pages/conversation/components/message/MessageReadStatus.vue index e67b38d33..678debd26 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageReadStatus.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageReadStatus.vue @@ -60,7 +60,7 @@ import { CommonStatusEnum } from '@/utils/constants' import { ImConversationType, ImGroupReceiptStatus } from '../../../../../utils/constants' import type { Message } from '../../../../types' import { useConversationStore } from '../../../../store/conversationStore' -import GroupMember, { type GroupMemberLite } from '../../../../components/GroupMember.vue' +import GroupMember, { type GroupMemberLite } from '../../../../components/group/GroupMember.vue' import PagedScroller from '../../../../components/PagedScroller.vue' defineOptions({ name: 'ImMessageReadStatus' }) diff --git a/src/views/im/home/pages/conversation/index.vue b/src/views/im/home/pages/conversation/index.vue index 0d38785a6..f8a85e481 100644 --- a/src/views/im/home/pages/conversation/index.vue +++ b/src/views/im/home/pages/conversation/index.vue @@ -51,8 +51,8 @@ - - + { ) }) -/** CreateGroupDialog 需要全量好友列表来勾选成员,结构与 friend / group Tab 保持一致 */ +/** GroupCreateDialog 需要全量好友列表来勾选成员,结构与通讯录里好友/群分组保持一致 */ const friends = computed(() => friendStore.getActiveFriends.map((friend: Friend) => ({ id: friend.friendUserId, @@ -112,7 +111,7 @@ const friends = computed(() => /** 处理建群成功 */ function handleGroupCreated(groupId: number) { - // CreateGroupDialog 已经 upsertGroup 把新群写进 store,这里只 get + 打开会话 + // GroupCreateDialog 已经 upsertGroup 把新群写进 store,这里只 get + 打开会话 const group = groupStore.getGroup(groupId) if (!group) { return diff --git a/src/views/im/home/pages/friend/FriendPage.vue b/src/views/im/home/pages/friend/FriendPage.vue deleted file mode 100644 index 0befbe68b..000000000 --- a/src/views/im/home/pages/friend/FriendPage.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/src/views/im/home/pages/friend/components/FriendItem.vue b/src/views/im/home/pages/friend/components/FriendItem.vue deleted file mode 100644 index 279302f7a..000000000 --- a/src/views/im/home/pages/friend/components/FriendItem.vue +++ /dev/null @@ -1,129 +0,0 @@ - - - - - diff --git a/src/views/im/home/pages/group/GroupPage.vue b/src/views/im/home/pages/group/GroupPage.vue deleted file mode 100644 index 539f731a5..000000000 --- a/src/views/im/home/pages/group/GroupPage.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/src/views/im/home/pages/group/components/GroupItem.vue b/src/views/im/home/pages/group/components/GroupItem.vue deleted file mode 100644 index be8ba9bb2..000000000 --- a/src/views/im/home/pages/group/components/GroupItem.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - - diff --git a/src/views/im/home/pages/group/components/GroupMemberGrid.vue b/src/views/im/home/pages/group/components/GroupMemberGrid.vue deleted file mode 100644 index ed759a165..000000000 --- a/src/views/im/home/pages/group/components/GroupMemberGrid.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/src/views/im/home/pages/group/components/GroupMemberItem.vue b/src/views/im/home/pages/group/components/GroupMemberItem.vue deleted file mode 100644 index 049078281..000000000 --- a/src/views/im/home/pages/group/components/GroupMemberItem.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - - - diff --git a/src/views/im/home/store/uiStore.ts b/src/views/im/home/store/uiStore.ts index 9e223808e..a45f27a78 100644 --- a/src/views/im/home/store/uiStore.ts +++ b/src/views/im/home/store/uiStore.ts @@ -1,7 +1,7 @@ import { defineStore, acceptHMRUpdate } from 'pinia' import { reactive } from 'vue' -import type { UserInfo } from '../types' +import type { User } from '../types' /** * IM 全局 UI store @@ -15,12 +15,12 @@ export const useImUiStore = defineStore('imUiStore', () => { // 用户名片悬浮卡:头像 / 昵称等触发点遍布会话、群成员、@ 选择器等列表, const userInfoCard = reactive({ show: false, - user: null as UserInfo | null, + user: null as User | null, position: { x: 0, y: 0 } }) /** 打开用户名片 */ - function openUserInfoCard(user: UserInfo, position: { x: number; y: number }) { + function openUserInfoCard(user: User, position: { x: number; y: number }) { const viewportWidth = document.documentElement.clientWidth const viewportHeight = document.documentElement.clientHeight userInfoCard.user = user diff --git a/src/views/im/home/types/index.ts b/src/views/im/home/types/index.ts index 1ad7de5e3..04527c757 100644 --- a/src/views/im/home/types/index.ts +++ b/src/views/im/home/types/index.ts @@ -156,11 +156,41 @@ export interface Friend { // ==================== 用户名片 ==================== // 用户精简信息(对齐后端 UserSimpleRespVO,名片 / 头像 hover 等场景共用) -export interface UserInfo { +export interface User { id: number nickname?: string avatar?: string sex?: number deptId?: number deptName?: string +} + +// ==================== 列表行展示用 Lite 类型 ==================== + +/** + * 好友列表行:从 Friend 派生的展示快照 + * - id 用 friendUserId(与列表 click / 选中比对一致),不是 Friend.id(关系记录主键) + * - deleted 派生自 Friend.status === DISABLE(软删保留),调用方按场景过滤 + */ +export interface FriendLite { + id: number + nickname: string + avatar?: string + displayName?: string + deleted?: boolean +} + +/** + * 群列表行:从 Group 派生的展示快照 + * - showGroupName / showImage:调用方决定带不带备注(如个人备注群名);展示按 show* > 原值兜底 + * - showImageThumb:高频列表用缩略图,避免拉原图阻塞滚动 + */ +export interface GroupLite { + id: number + name?: string + showGroupName?: string + showImage?: string + showImageThumb?: string + memberCount?: number + ownerId?: number } \ No newline at end of file