feat(im): 前端的 IM 增加 Layout 整体布局

im
YunaiV 2026-04-19 00:30:07 +08:00
parent 518851ce74
commit 9fb796194e
7 changed files with 275 additions and 20 deletions

View File

@ -0,0 +1,53 @@
import request from '@/config/axios'
// 群聊消息发送 Request VO
export interface ImGroupMessageSendReqVO {
clientMessageId: string // 客户端消息编号
groupId: number // 群编号
type: number // 消息类型
content: string // 消息内容
atUserIds?: number[] // @ 用户编号列表
receiverUserIds?: number[] // 定向接收用户编号列表
needReceipt?: boolean // 是否需要回执
}
// 群聊消息 Response VO
export interface ImGroupMessageRespVO {
id: string // 消息编号
clientMessageId: string // 客户端消息编号
senderId: string // 发送人编号
groupId: string // 群编号
type: number // 消息类型
content: string // 消息内容
status: number // 消息状态
sendTime: string // 发送时间
atUserIds?: number[] // @ 用户编号列表
receiverUserIds?: number[] // 定向接收用户编号列表
receiptStatus?: number // 回执状态
readCount?: number // 已读人数
}
// 发送群聊消息
export const sendGroupMessage = (data: ImGroupMessageSendReqVO) => {
return request.post<ImGroupMessageRespVO>({ url: '/im/message/group/send', data })
}
// 增量拉取群聊消息
export const pullGroupMessages = (params: { minId: string; size: number }) => {
return request.get<ImGroupMessageRespVO[]>({ url: '/im/message/group/pull', params })
}
// 标记群聊消息已读
export const readGroupMessages = (groupId: string) => {
return request.put({ url: '/im/message/group/read', params: { groupId } })
}
// 撤回群聊消息
export const recallGroupMessage = (id: string) => {
return request.delete({ url: '/im/message/group/recall', params: { id } })
}
// 查询群消息已读用户列表
export const getGroupReadUsers = (params: { groupId: string; messageId: string }) => {
return request.get<string[]>({ url: '/im/message/group/read-users', params })
}

View File

@ -0,0 +1,41 @@
import request from '@/config/axios'
// 私聊消息发送 Request VO
export interface ImPrivateMessageSendReqVO {
clientMessageId: string // 客户端消息编号
receiverId: number // 接收人编号
type: number // 消息类型
content: string // 消息内容
}
// 私聊消息 Response VO
export interface ImPrivateMessageRespVO {
id: number // 消息编号
clientMessageId: string // 客户端消息编号
senderId: number // 发送人编号
receiverId: number // 接收人编号
type: number // 消息类型
content: string // 消息内容
status: number // 消息状态
sendTime: string // 发送时间
}
// 发送私聊消息
export const sendPrivateMessage = (data: ImPrivateMessageSendReqVO) => {
return request.post<ImPrivateMessageRespVO>({ url: '/im/message/private/send', data })
}
// 增量拉取私聊消息
export const pullPrivateMessages = (params: { minId: string; size: number }) => {
return request.get<ImPrivateMessageRespVO[]>({ url: '/im/message/private/pull', params })
}
// 标记私聊消息已读
export const readPrivateMessages = (friendId: string) => {
return request.put({ url: '/im/message/private/read', params: { friendId } })
}
// 撤回私聊消息
export const recallPrivateMessage = (id: string) => {
return request.delete({ url: '/im/message/private/recall', params: { id } })
}

View File

@ -1,7 +1,7 @@
<script lang="tsx">
import { defineComponent, computed } from 'vue'
import { useRouter } from 'vue-router'
import { Message } from '@/layout/components//Message'
import router from '@/router'
import { Message } from '@/layout/components/Message'
import { Collapse } from '@/layout/components/Collapse'
import { UserInfo } from '@/layout/components/UserInfo'
import { Screenfull } from '@/layout/components/Screenfull'
@ -45,15 +45,19 @@ const locale = computed(() => appStore.getLocale)
//
const message = computed(() => appStore.getMessage)
const goToChat = () => {
window.open(window.location.host + '/chat', '_blank')
}
//
const hasTenantVisitPermission = computed(
() => import.meta.env.VITE_APP_TENANT_ENABLE === 'true' && checkPermi(['system:tenant:visit'])
)
// name resolve URL IM
// IM push Tab
const goToChat = () => {
// name resolve URL IM
const { href } = router.resolve({ name: 'ImHome' })
window.open(href, '_blank')
}
export default defineComponent({
name: 'ToolHeader',
setup() {
@ -75,18 +79,13 @@ export default defineComponent({
</div>
) : undefined}
<div class="h-full flex items-center">
<div onClick={goToChat}>
<Icon
icon="ep:chat-dot-round"
color="var(--top-header-text-color)"
class="custom-hover"
/>
</div>
{hasTenantVisitPermission.value ? <TenantVisit /> : undefined}
{screenfull.value ? (
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
) : undefined}
{search.value ? <RouterSearch isModal={false} color="var(--top-header-text-color)"/> : undefined}
{search.value ? (
<RouterSearch isModal={false} color="var(--top-header-text-color)" />
) : undefined}
{size.value ? (
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
) : undefined}
@ -99,6 +98,10 @@ export default defineComponent({
{message.value ? (
<Message class="custom-hover" color="var(--top-header-text-color)"></Message>
) : undefined}
{/* IM 聊天入口 */}
<div class="custom-hover" onClick={goToChat}>
<Icon color="var(--top-header-text-color)" size={18} icon="ep:chat-dot-round" />
</div>
<UserInfo></UserInfo>
</div>
</div>

View File

@ -1,5 +1,4 @@
import { Layout } from '@/utils/routerHelper'
import ChatPage from '../../views/chat/ChatPage/Index.vue'
const { t } = useI18n()
/**
@ -747,11 +746,107 @@ const remainingRouter: AppRouteRecordRaw[] = [
component: () => import('@/views/iot/ota/firmware/detail/index.vue')
}
]
}, {
path: '/chat',
component: ChatPage,
name: 'chat',
meta: { hidden: false }
},
{
// 统一 /im 分组:下分 home聊天壳+ managerLayout 管理壳)
path: '/im',
name: 'Im',
redirect: '/im/home/message',
meta: { hidden: false, title: 'IM 即时通讯' },
children: [
{
// 聊天壳:全屏沉浸式应用,带 ToolBar + keep-alive
// hidden:true 不在 yudao 侧边栏菜单显示;三个子 Tab 通过内部 ToolBar 切换
path: 'home',
component: () => import('@/views/im/home/Index.vue'),
name: 'ImHome',
redirect: '/im/home/message',
meta: { hidden: true, title: '聊天' },
children: [
{
path: 'message',
component: () => import('@/views/im/home/pages/MessagePage.vue'),
name: 'ImHomeMessage',
meta: { hidden: true, title: '消息' }
},
{
path: 'friend',
component: () => import('@/views/im/home/pages/FriendPage.vue'),
name: 'ImHomeFriend',
meta: { hidden: true, title: '好友' }
},
{
path: 'group',
component: () => import('@/views/im/home/pages/GroupPage.vue'),
name: 'ImHomeGroup',
meta: { hidden: true, title: '群聊' }
}
]
},
{
path: 'manager/message',
component: Layout,
name: 'ImManagerMessage',
redirect: '/im/manager/message/index',
meta: { hidden: false },
children: [
{
path: 'index',
component: () => import('@/views/im/manager/message/index.vue'),
name: 'ImManagerMessageIndex',
meta: {
canTo: true,
hidden: false,
noTagsView: false,
icon: 'ep:chat-dot-round',
title: '消息管理'
}
}
]
},
{
path: 'manager/friend',
component: Layout,
name: 'ImManagerFriend',
redirect: '/im/manager/friend/index',
meta: { hidden: false },
children: [
{
path: 'index',
component: () => import('@/views/im/manager/friend/index.vue'),
name: 'ImManagerFriendIndex',
meta: {
canTo: true,
hidden: false,
noTagsView: false,
icon: 'ep:user',
title: '好友管理'
}
}
]
},
{
path: 'manager/group',
component: Layout,
name: 'ImManagerGroup',
redirect: '/im/manager/group/index',
meta: { hidden: false },
children: [
{
path: 'index',
component: () => import('@/views/im/manager/group/index.vue'),
name: 'ImManagerGroupIndex',
meta: {
canTo: true,
hidden: false,
noTagsView: false,
icon: 'ep:user-filled',
title: '群管理'
}
}
]
}
]
}
]

View File

@ -0,0 +1,21 @@
<template>
<ContentWrap>
<!-- TODO @芋艿这个只是临时的 -->
<div class="im-manager-placeholder">我是好友管理测试界面</div>
</ContentWrap>
</template>
<script lang="ts" setup>
defineOptions({ name: 'ImManagerFriend' })
</script>
<style scoped>
.im-manager-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
font-size: 18px;
color: #606266;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<ContentWrap>
<!-- TODO @芋艿这个只是临时的 -->
<div class="im-manager-placeholder">我是群管理测试界面</div>
</ContentWrap>
</template>
<script lang="ts" setup>
defineOptions({ name: 'ImManagerGroup' })
</script>
<style scoped>
.im-manager-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
font-size: 18px;
color: #606266;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<ContentWrap>
<!-- TODO @芋艿这个只是临时的 -->
<div class="im-manager-placeholder">我是消息管理测试界面</div>
</ContentWrap>
</template>
<script lang="ts" setup>
defineOptions({ name: 'ImManagerMessage' })
</script>
<style scoped>
.im-manager-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
font-size: 18px;
color: #606266;
}
</style>