新增:接收 websocket 消息
parent
569b1b64bd
commit
063a61644a
|
@ -26,17 +26,22 @@ export interface ImMessageRespVO {
|
||||||
sequence: number // 序号
|
sequence: number // 序号
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface pullParams {
|
||||||
|
sequence: number
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
export const sendMessage = async (data: ImMessageSendReqVO) => {
|
export const sendMessage = async (data: ImMessageSendReqVO): Promise<ImMessageSendRespVO> => {
|
||||||
return await request.post({ url: `/im/message/send`, data })
|
return await request.post({ url: `/im/message/send`, data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消息列表-拉取大于 sequence 的消息列表
|
// 消息列表-拉取大于 sequence 的消息列表
|
||||||
export const pullMessageList = async (params: { sequence: number; size: number }) => {
|
export const pullMessageList = async (params: pullParams): Promise<ImMessageRespVO[]> => {
|
||||||
return await request.get({ url: `/im/message/pull`, params })
|
return await request.get({ url: `/im/message/pull`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消息列表-根据接收人和发送时间进行分页查询
|
// 消息列表-根据接收人和发送时间进行分页查询
|
||||||
export const getMessageList = async (params: any) => {
|
export const getMessageList = async (params: any): Promise<ImMessageRespVO[]> => {
|
||||||
return await request.get({ url: `/im/message/list`, params })
|
return await request.get({ url: `/im/message/list`, params })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Dialog } from '@/components/Dialog'
|
import { Dialog } from '@/components/Dialog'
|
||||||
import { shallowRef, defineAsyncComponent, DefineComponent } from 'vue'
|
import { shallowRef, defineAsyncComponent, DefineComponent } from 'vue'
|
||||||
|
import { webSocketStore } from '@/store/modules/webSocketStore'
|
||||||
|
|
||||||
// 异步加载可能的对话框内容组件
|
// 异步加载可能的对话框内容组件
|
||||||
const IMComponent = defineAsyncComponent(() => import('@/views/im/index.vue'))
|
const IMComponent = defineAsyncComponent(() => import('@/views/im/index.vue'))
|
||||||
|
@ -13,6 +14,17 @@ function openDialog() {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
currentComponent.value = IMComponent // 加载 IM 组件
|
currentComponent.value = IMComponent // 加载 IM 组件
|
||||||
}
|
}
|
||||||
|
// 登录成功后初始化 WebSocket 连接
|
||||||
|
const { status, data, initWebSocket } = webSocketStore()
|
||||||
|
|
||||||
|
/** 监听接收到的数据 */
|
||||||
|
const messageList = ref([] as { time: number; text: string }[]) // 消息列表
|
||||||
|
|
||||||
|
// ========== 初始化 =========
|
||||||
|
onMounted(() => {
|
||||||
|
// 首次加载 websocket
|
||||||
|
initWebSocket()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export interface ImMessageWebSocket {
|
||||||
|
id: number // 编号
|
||||||
|
conversationType: number // 会话类型
|
||||||
|
senderId: number // 发送人编号
|
||||||
|
senderNickname: string // 发送人昵称
|
||||||
|
senderAvatar: string // 发送人头像
|
||||||
|
receiverId: number // 接收人编号
|
||||||
|
contentType: number // 内容类型
|
||||||
|
content: string // 内容
|
||||||
|
sendTime: string // 发送时间
|
||||||
|
sequence: number // 序号
|
||||||
|
}
|
||||||
|
|
||||||
|
export const imMessageStore = defineStore({
|
||||||
|
id: 'imMessage',
|
||||||
|
state: () => ({
|
||||||
|
messages: [] as ImMessageWebSocket[]
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
addMessage(message: ImMessageWebSocket) {
|
||||||
|
this.messages.push(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useWebSocket, WebSocketStatus } from '@vueuse/core'
|
||||||
|
import { getAccessToken } from '@/utils/auth'
|
||||||
|
import { imMessageStore, ImMessageWebSocket } from './imMessageStore'
|
||||||
|
|
||||||
|
// 从 imMessageStore 中导入 addMessage 方法
|
||||||
|
const { addMessage } = imMessageStore()
|
||||||
|
export const webSocketStore = defineStore({
|
||||||
|
id: 'websocket',
|
||||||
|
state: () => ({
|
||||||
|
data: ref(null) as any,
|
||||||
|
status: ref<WebSocketStatus> as any,
|
||||||
|
close: null as ((code?: number | undefined, reason?: string | undefined) => void) | null,
|
||||||
|
open: null as (() => void) | null,
|
||||||
|
send: null as
|
||||||
|
| ((data: string | ArrayBuffer | Blob, useBuffer?: boolean | undefined) => boolean)
|
||||||
|
| null,
|
||||||
|
pingInterval: null as NodeJS.Timeout | null
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
initWebSocket() {
|
||||||
|
const server =
|
||||||
|
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
|
||||||
|
'?token=' +
|
||||||
|
getAccessToken(),
|
||||||
|
{ data, status, close, open, send } = useWebSocket(server, {
|
||||||
|
autoReconnect: true,
|
||||||
|
heartbeat: true,
|
||||||
|
onMessage(ws, event) {
|
||||||
|
// ws 状态不是 open 时不处理
|
||||||
|
if (ws.readyState !== WebSocket.OPEN) return
|
||||||
|
try {
|
||||||
|
// 1. 收到心跳
|
||||||
|
if (data.value === 'pong') {
|
||||||
|
console.log('websocket 收到心跳包')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('websocket 收到消息', event.data)
|
||||||
|
// 2.1 解析 type 消息类型
|
||||||
|
const jsonMessage = JSON.parse(data.value)
|
||||||
|
const type = jsonMessage.type
|
||||||
|
const content = JSON.parse(jsonMessage.content)
|
||||||
|
if (!type) {
|
||||||
|
// message.error('未知的消息类型:' + data.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2.2 消息类型:demo-message-receive
|
||||||
|
if (type === 'im-message-receive') {
|
||||||
|
const message: ImMessageWebSocket = {
|
||||||
|
id: content.id,
|
||||||
|
conversationType: content.conversationType,
|
||||||
|
senderId: content.senderId,
|
||||||
|
senderNickname: content.senderNickname,
|
||||||
|
senderAvatar: content.senderAvatar,
|
||||||
|
receiverId: content.receiverId,
|
||||||
|
contentType: content.contentType,
|
||||||
|
content: content.content,
|
||||||
|
sendTime: content.sendTime,
|
||||||
|
sequence: content.sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到 pina imMessageStore
|
||||||
|
addMessage(message)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.status = status
|
||||||
|
this.data = data
|
||||||
|
this.send = send
|
||||||
|
this.close = close
|
||||||
|
this.open = open
|
||||||
|
|
||||||
|
console.log('websocket 初始化成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script setup>
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import router from '@/router'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||||
|
import { messageType } from '@/constant'
|
||||||
|
/* 单人头像 */
|
||||||
|
import defaultSingleAvatar from '@/assets/imgs/im/avatar/theme2x.png'
|
||||||
|
/* store */
|
||||||
|
const store = useStore()
|
||||||
|
/* route */
|
||||||
|
const route = useRoute()
|
||||||
|
const { CHAT_TYPE } = messageType
|
||||||
|
|
||||||
|
/* 进入会话 */
|
||||||
|
const toChatMessage = () => {
|
||||||
|
console.log('>>>>>>>...route.query')
|
||||||
|
router.push({
|
||||||
|
path: '/chat/conversation/message',
|
||||||
|
query: {
|
||||||
|
id: route.query.id,
|
||||||
|
chatType: route.query.chatType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app_container">
|
||||||
|
<el-header class="contactInfo_header">
|
||||||
|
<el-page-header style="margin-top: 12px" :icon="ArrowLeft" @click="$router.back(-1)" />
|
||||||
|
<el-divider />
|
||||||
|
</el-header>
|
||||||
|
<el-main class="contactInfo_main">
|
||||||
|
<div class="contactInfo_main_card">
|
||||||
|
<div class="contactInfo_box">
|
||||||
|
<div class="avatar">
|
||||||
|
<el-avatar
|
||||||
|
class="avatar_img"
|
||||||
|
:src="nowContactInfo.avatarurl ? nowContactInfo.avatarurl : defaultSingleAvatar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
nowContactInfo.nickname
|
||||||
|
? `${nowContactInfo.nickname}(${nowContactInfo.hxId})`
|
||||||
|
: nowContactInfo.hxId
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contaactInfo_btn">
|
||||||
|
<el-button type="primary" size="large" @click="toChatMessage">发起会话 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app_container {
|
||||||
|
background: #f1f2f4;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 0 5px 5px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.contactInfo_header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contactInfo_main {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.contactInfo_main_card {
|
||||||
|
width: 100%;
|
||||||
|
height: 90%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.5s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 12px 12px 2px 1px rgba(125, 125, 126, 0.068);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contactInfo_box {
|
||||||
|
width: 80%;
|
||||||
|
min-height: 500px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar > .avatar_img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.func_box {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.single_func {
|
||||||
|
height: 100px;
|
||||||
|
// background: #000;
|
||||||
|
margin-top: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.add_black_list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.del_friend {
|
||||||
|
width: 100%;
|
||||||
|
color: red;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contaactInfo_btn {
|
||||||
|
width: 80%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//干掉原有样式里面的竖线
|
||||||
|
::v-deep .el-page-header__left::after {
|
||||||
|
width: 0px !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,116 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
// import router from '@/router'
|
||||||
|
import { messageType } from '@/constant'
|
||||||
|
/* 默认头像 */
|
||||||
|
import defaultAvatar from '@/assets/images/avatar/theme2x.png'
|
||||||
|
/* store */
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
//处理friendList进行分类处理
|
||||||
|
const classifyFriendList = computed(() => store.getters.sortedFriendList)
|
||||||
|
//点击对应联系人跳转至用户详情页
|
||||||
|
const { CHAT_TYPE } = messageType
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="friendItem_container">
|
||||||
|
<div
|
||||||
|
v-for="(friendName, friendItemKey) in classifyFriendList"
|
||||||
|
:key="friendItemKey"
|
||||||
|
>
|
||||||
|
<div class="friend_main">
|
||||||
|
<div class="friend_title">
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
friendItemKey === ' '
|
||||||
|
? '#'
|
||||||
|
: friendItemKey.toUpperCase()
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<el-divider style="margin: 0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<el-col
|
||||||
|
class="friendItem_box"
|
||||||
|
:span="24"
|
||||||
|
v-for="item in friendName"
|
||||||
|
:key="item.hxId"
|
||||||
|
@click="
|
||||||
|
$emit('toContacts', {
|
||||||
|
id: item.hxId,
|
||||||
|
chatType: CHAT_TYPE.SINGLE
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-avatar
|
||||||
|
style="margin-right: 11px"
|
||||||
|
:size="33.03"
|
||||||
|
:src="
|
||||||
|
item.avatarurl ? item.avatarurl : defaultAvatar
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</el-avatar>
|
||||||
|
<span class="friend_name">
|
||||||
|
{{ item.nickname ? item.nickname : item.hxId }}
|
||||||
|
</span>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.friendItem_box {
|
||||||
|
height: 66px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
// background: #000;
|
||||||
|
padding: 0 22px;
|
||||||
|
background: #efefef;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
/* identical to box height */
|
||||||
|
text-align: center;
|
||||||
|
color: #333333;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.friend_name {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: left;
|
||||||
|
width: 80%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #dcdcdc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend_main {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.friend_title {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
opacity: 0.21;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-left: 23px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 0.342857px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,30 @@
|
||||||
<script setup lang="ts"></script>
|
<template>
|
||||||
|
<el-container style="height: 100%">
|
||||||
|
<el-aside class="contacts_box">
|
||||||
|
<SearchInput :searchType="'contacts'" />
|
||||||
|
<el-scrollbar class="contacts_collapse" tag="div" :always="false">
|
||||||
|
<!-- 联系人群组列表 -->
|
||||||
|
<el-collapse />
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-aside>
|
||||||
|
<el-main class="contacts_infors_main_box" />
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
/* 相关组件 */
|
||||||
|
import { SearchInput } from '@/components/Im/SearchInput'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
<template></template>
|
//好友列表
|
||||||
|
const friendList = computed(() => {})
|
||||||
<style scoped lang="scss"></style>
|
const getUserList = async () => {
|
||||||
|
const data = await UserApi.getSimpleUserList()
|
||||||
|
console.log('data', data)
|
||||||
|
friendList.value = data
|
||||||
|
}
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
getUserList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
|
@ -116,9 +116,9 @@ const sendTextMessage = _.debounce(async () => {
|
||||||
messageQuoteRef.value?.clearQuoteContent()
|
messageQuoteRef.value?.clearQuoteContent()
|
||||||
try {
|
try {
|
||||||
console.log('imMessageSendReqVO', imMessageSendReqVO)
|
console.log('imMessageSendReqVO', imMessageSendReqVO)
|
||||||
await MessageApi.sendMessage(imMessageSendReqVO)
|
const imMessageSendRespVO = await MessageApi.sendMessage(imMessageSendReqVO)
|
||||||
|
console.log('>>>>>发送成功', imMessageSendRespVO)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//handleSDKErrorNotifi(error.type, error.message)
|
|
||||||
console.log('>>>>>>>发送失败+++++++', error)
|
console.log('>>>>>>>发送失败+++++++', error)
|
||||||
} finally {
|
} finally {
|
||||||
isAtAll.value = false
|
isAtAll.value = false
|
||||||
|
|
|
@ -25,21 +25,20 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
const { nowPickInfo } = toRefs(props)
|
const { nowPickInfo } = toRefs(props)
|
||||||
const { messageData } = toRefs(props)
|
const { messageData } = toRefs(props)
|
||||||
|
console.log('>>>>>messageData', messageData)
|
||||||
/* 处理时间显示间隔 */
|
/* 处理时间显示间隔 */
|
||||||
const handleMsgTimeShow = (time, index) => {
|
const handleMsgTimeShow = (sendTime, index) => {
|
||||||
console.log('>>>>>时间显示', time, index)
|
|
||||||
const msgList = Array.from(messageData.value)
|
const msgList = Array.from(messageData.value)
|
||||||
if (index !== 0) {
|
if (index !== 0) {
|
||||||
const lastTime = msgList[index - 1].time
|
const lastTime = msgList[index - 1].sendTime
|
||||||
console.log('>>>>>时间间隔', time - lastTime, time, lastTime)
|
return sendTime - lastTime > 50000 ? formatDate(sendTime, 'MM/DD/HH:mm') : ''
|
||||||
return time - lastTime > 50000 ? formatDate(time, 'MM/DD/HH:mm') : ''
|
|
||||||
} else {
|
} else {
|
||||||
return formatDate(time, 'MM/DD/HH:mm')
|
return formatDate(sendTime, 'MM/DD/HH:mm')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* computed-- 消息来源是否为自己 */
|
/* computed-- 消息来源是否为自己 */
|
||||||
const isMyself = (msgBody) => {
|
const isMyself = (msgBody) => {
|
||||||
return msgBody.from === '1'
|
return msgBody.senderId === userStore.user.id || msgBody.receiverId === userStore.user.id
|
||||||
}
|
}
|
||||||
/* 文本中是否包含link */
|
/* 文本中是否包含link */
|
||||||
const isLink = computed(() => {
|
const isLink = computed(() => {
|
||||||
|
@ -51,15 +50,11 @@ const isLink = computed(() => {
|
||||||
const loginUserInfo = {
|
const loginUserInfo = {
|
||||||
avatarurl: avatar.value
|
avatarurl: avatar.value
|
||||||
}
|
}
|
||||||
/* 获取他人的用户信息 */
|
|
||||||
const otherUserInfo = (from) => {
|
|
||||||
return {
|
|
||||||
avatarurl: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//处理聊天对方昵称展示
|
//处理聊天对方昵称展示
|
||||||
const handleNickName = (from) => {
|
const handleNickName = (msgBody) => {
|
||||||
return from === '1' ? '我' : '对方'
|
return msgBody.senderId === userStore.user.id || msgBody.receiverId === userStore.user.id
|
||||||
|
? '我'
|
||||||
|
: '对方'
|
||||||
}
|
}
|
||||||
//引用消息
|
//引用消息
|
||||||
let clickQuoteMsgId = ref('')
|
let clickQuoteMsgId = ref('')
|
||||||
|
@ -102,121 +97,40 @@ const startplayAudio = (msgBody) => {
|
||||||
>
|
>
|
||||||
<!-- 普通消息气泡 -->
|
<!-- 普通消息气泡 -->
|
||||||
<div
|
<div
|
||||||
v-if="!msgBody.isRecall && msgBody.type !== ALL_MESSAGE_TYPE.OA_NOTIFICATION"
|
v-if="msgBody.conversationType !== ALL_MESSAGE_TYPE.OA_NOTIFICATION"
|
||||||
class="message_box_item"
|
class="message_box_item"
|
||||||
:style="{
|
:style="{
|
||||||
flexDirection: isMyself(msgBody) ? 'row-reverse' : 'row'
|
flexDirection: isMyself(msgBody) ? 'row-reverse' : 'row'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="message_item_time">
|
<div class="message_item_time">
|
||||||
{{ handleMsgTimeShow(msgBody.time, index) }}
|
{{ handleMsgTimeShow(msgBody.sendTime, index) }}
|
||||||
</div>
|
</div>
|
||||||
<el-avatar
|
<el-avatar class="message_item_avator" :src="loginUserInfo.avatarurl" />
|
||||||
class="message_item_avator"
|
|
||||||
:src="
|
|
||||||
isMyself(msgBody)
|
|
||||||
? loginUserInfo.avatarurl
|
|
||||||
: otherUserInfo(msgBody.from).avatarurl || defaultAvatar
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<!-- 普通消息内容 -->
|
<!-- 普通消息内容 -->
|
||||||
<div class="message_box_card">
|
<div class="message_box_card">
|
||||||
<span v-show="!isMyself(msgBody)" class="message_box_nickname">{{
|
<span v-show="!isMyself(msgBody)" class="message_box_nickname">{{
|
||||||
handleNickName(msgBody.from)
|
handleNickName(msgBody)
|
||||||
}}</span>
|
}}</span>
|
||||||
<el-dropdown
|
<el-dropdown
|
||||||
class="message_box_content"
|
class="message_box_content"
|
||||||
:class="[
|
:class="[isMyself(msgBody) ? 'message_box_content_mine' : 'message_box_content_other']"
|
||||||
isMyself(msgBody) ? 'message_box_content_mine' : 'message_box_content_other',
|
|
||||||
clickQuoteMsgId === msgBody.id && 'quote_msg_avtive'
|
|
||||||
]"
|
|
||||||
trigger="contextmenu"
|
trigger="contextmenu"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
>
|
>
|
||||||
<!-- 文本类型消息 -->
|
<!-- 文本类型消息 -->
|
||||||
<p
|
<p
|
||||||
style="padding: 10px; line-height: 20px"
|
style="padding: 10px; line-height: 20px"
|
||||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT"
|
v-if="msgBody.contentType === ALL_MESSAGE_TYPE.TEXT"
|
||||||
>
|
>
|
||||||
<template v-if="!isLink(msgBody.msg)">
|
<template v-if="!isLink(msgBody.content)">
|
||||||
{{ msgBody.msg }}
|
{{ msgBody.content }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else> <span v-html="paseLink(msgBody.msg).msg"> </span></template>
|
<template v-else> <span v-html="paseLink(msgBody.content).msg"> </span></template>
|
||||||
</p>
|
</p>
|
||||||
<!-- 图片类型消息 -->
|
<!-- 图片类型消息 -->
|
||||||
<!-- <div> -->
|
|
||||||
<el-image
|
|
||||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.IMAGE"
|
|
||||||
style="border-radius: 5px"
|
|
||||||
:src="msgBody.thumb"
|
|
||||||
:preview-src-list="[msgBody.url]"
|
|
||||||
:initial-index="1"
|
|
||||||
fit="cover"
|
|
||||||
/>
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- 语音类型消息 -->
|
<!-- 语音类型消息 -->
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'message_box_content_audio',
|
|
||||||
isMyself(msgBody)
|
|
||||||
? 'message_box_content_audio_mine'
|
|
||||||
: 'message_box_content_audio_other'
|
|
||||||
]"
|
|
||||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.AUDIO"
|
|
||||||
@click="startplayAudio(msgBody)"
|
|
||||||
:style="`width:${msgBody.length * 10}px`"
|
|
||||||
>
|
|
||||||
<span class="audio_length_text"> {{ msgBody.length }}′′ </span>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
isMyself(msgBody) ? 'play_audio_icon_mine' : 'play_audio_icon_other',
|
|
||||||
audioPlayStatus.playMsgId === msgBody.id && 'start_play_audio'
|
|
||||||
]"
|
|
||||||
style="background-size: 100% 100%"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.LOCAL">
|
|
||||||
<p style="padding: 10px">[暂不支持位置消息展示]</p>
|
|
||||||
</div>
|
|
||||||
<!-- 文件类型消息 -->
|
<!-- 文件类型消息 -->
|
||||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.FILE" class="message_box_content_file">
|
|
||||||
<div class="file_text_box">
|
|
||||||
<div class="file_name">
|
|
||||||
{{ msgBody.filename }}
|
|
||||||
</div>
|
|
||||||
<div class="file_size">
|
|
||||||
{{ fileSizeFormat(msgBody.file_length) }}
|
|
||||||
</div>
|
|
||||||
<a class="file_download" :href="msgBody.url" download>点击下载</a>
|
|
||||||
</div>
|
|
||||||
<span class="iconfont icon-wenjian"></span>
|
|
||||||
</div>
|
|
||||||
<!-- 自定义类型消息 -->
|
|
||||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.CUSTOM" class="message_box_content_custom">
|
|
||||||
<template v-if="msgBody.customEvent && CUSTOM_TYPE[msgBody.customEvent]">
|
|
||||||
<div class="user_card">
|
|
||||||
<div class="user_card_main">
|
|
||||||
<!-- 头像 -->
|
|
||||||
<el-avatar
|
|
||||||
shape="circle"
|
|
||||||
:size="50"
|
|
||||||
:src="
|
|
||||||
(msgBody.customExts && msgBody.customExts.avatarurl) ||
|
|
||||||
msgBody.customExts.avatar ||
|
|
||||||
defaultAvatar
|
|
||||||
"
|
|
||||||
fit="cover"
|
|
||||||
/>
|
|
||||||
<!-- 昵称 -->
|
|
||||||
<span class="nickname">{{
|
|
||||||
(msgBody.customExts && msgBody.customExts.nickname) || msgBody.customExts.uid
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<el-divider style="margin: 5px 0; border-top: 1px solid black" />
|
|
||||||
<p style="font-size: 8px">个人名片</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { messageType } from '@/constant/im'
|
||||||
import MessageList from './components/messageList/index.vue'
|
import MessageList from './components/messageList/index.vue'
|
||||||
import InputBox from './components/inputBox/index.vue'
|
import InputBox from './components/inputBox/index.vue'
|
||||||
import * as MessageApi from '@/api/im/message'
|
import * as MessageApi from '@/api/im/message'
|
||||||
|
import { ImMessageRespVO } from '@/api/im/message'
|
||||||
|
|
||||||
const { query } = useRoute() // 查询参数
|
const { query } = useRoute() // 查询参数
|
||||||
|
|
||||||
|
@ -25,10 +26,10 @@ const delTheFriend = () => {
|
||||||
}
|
}
|
||||||
// 当前聊天对象信息
|
// 当前聊天对象信息
|
||||||
const nowPickInfo = ref({
|
const nowPickInfo = ref({
|
||||||
id: 1,
|
id: 100,
|
||||||
chatType: CHAT_TYPE.SINGLE,
|
chatType: CHAT_TYPE.SINGLE,
|
||||||
userInfo: {
|
userInfo: {
|
||||||
nickname: '芋道源码',
|
nickname: '芋道',
|
||||||
userStatus: '1'
|
userStatus: '1'
|
||||||
},
|
},
|
||||||
groupDetail: {
|
groupDetail: {
|
||||||
|
@ -42,78 +43,16 @@ const nowPickInfo = ref({
|
||||||
const groupDetail = computed(() => {
|
const groupDetail = computed(() => {
|
||||||
return nowPickInfo.value.groupDetail
|
return nowPickInfo.value.groupDetail
|
||||||
})
|
})
|
||||||
//获取其id对应的消息内容
|
//获取对应的消息内容
|
||||||
const messageData = computed(() => [
|
const pullParams = reactive({
|
||||||
{
|
sequence: 0,
|
||||||
id: 1,
|
size: 100
|
||||||
type: ALL_MESSAGE_TYPE.TEXT,
|
})
|
||||||
isRecall: false,
|
const messageData = ref([])
|
||||||
time: '1711944110000',
|
const getMessageData = async () => {
|
||||||
from: '1',
|
messageData.value = await MessageApi.pullMessageList(pullParams)
|
||||||
msg: 'Hello, world!111',
|
}
|
||||||
modifiedInfo: {
|
console.log(messageData)
|
||||||
operationCount: 1
|
|
||||||
},
|
|
||||||
customExts: {
|
|
||||||
nickname: '芋道源码',
|
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
},
|
|
||||||
customEvent: {
|
|
||||||
type: '1',
|
|
||||||
data: {
|
|
||||||
type: '1',
|
|
||||||
data: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
file_length: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
type: ALL_MESSAGE_TYPE.TEXT,
|
|
||||||
isRecall: false,
|
|
||||||
time: '1711944221000',
|
|
||||||
from: '2',
|
|
||||||
msg: 'Hi, there!222',
|
|
||||||
modifiedInfo: {
|
|
||||||
operationCount: 0
|
|
||||||
},
|
|
||||||
customExts: {
|
|
||||||
nickname: '芋道源码',
|
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
},
|
|
||||||
customEvent: {
|
|
||||||
type: '1',
|
|
||||||
data: {
|
|
||||||
type: '1',
|
|
||||||
data: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
file_length: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
type: ALL_MESSAGE_TYPE.TEXT,
|
|
||||||
isRecall: false,
|
|
||||||
time: '1711944332000',
|
|
||||||
from: '1',
|
|
||||||
msg: 'Hello, world!333',
|
|
||||||
modifiedInfo: {
|
|
||||||
operationCount: 0
|
|
||||||
},
|
|
||||||
customExts: {
|
|
||||||
nickname: '芋道源码',
|
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
},
|
|
||||||
customEvent: {
|
|
||||||
type: '1',
|
|
||||||
data: {
|
|
||||||
type: '1',
|
|
||||||
data: 'https://avatars.githubusercontent.com/u/2?v=4'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
file_length: 0
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
/* 消息相关 */
|
/* 消息相关 */
|
||||||
const loadingHistoryMsg = ref(false) //是否正在加载中
|
const loadingHistoryMsg = ref(false) //是否正在加载中
|
||||||
|
@ -124,6 +63,7 @@ const fechHistoryMessage = (loadType) => {
|
||||||
console.log(loadType)
|
console.log(loadType)
|
||||||
console.log('加载更多')
|
console.log('加载更多')
|
||||||
loadingHistoryMsg.value = true
|
loadingHistoryMsg.value = true
|
||||||
|
getMessageData()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loadingHistoryMsg.value = false
|
loadingHistoryMsg.value = false
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
@ -137,6 +77,10 @@ const inputBox = ref(null)
|
||||||
const reEditMessage = (msg) => (inputBox.value.textContent = msg)
|
const reEditMessage = (msg) => (inputBox.value.textContent = msg)
|
||||||
//消息引用
|
//消息引用
|
||||||
const messageQuote = (msg) => inputBox.value.handleQuoteMessage(msg)
|
const messageQuote = (msg) => inputBox.value.handleQuoteMessage(msg)
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getMessageData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,17 +1,3 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { shallowRef, defineAsyncComponent, DefineComponent } from 'vue'
|
|
||||||
import NavBar from './NavBar/index.vue'
|
|
||||||
|
|
||||||
// 定义异步加载的组件
|
|
||||||
const ConversationComponent = defineAsyncComponent(
|
|
||||||
() => import('@/views/im/Conversation/index.vue')
|
|
||||||
)
|
|
||||||
|
|
||||||
const currentComponent = shallowRef<DefineComponent | null>(ConversationComponent) // 默认加载对话组件
|
|
||||||
|
|
||||||
defineOptions({ name: 'IM' })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-container class="chat_container">
|
<el-container class="chat_container">
|
||||||
|
@ -24,7 +10,19 @@ defineOptions({ name: 'IM' })
|
||||||
</el-container>
|
</el-container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { shallowRef, defineAsyncComponent, DefineComponent } from 'vue'
|
||||||
|
import NavBar from './NavBar/index.vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'IM' })
|
||||||
|
|
||||||
|
// 定义异步加载的组件
|
||||||
|
const conversationComponent = defineAsyncComponent(
|
||||||
|
() => import('@/views/im/Conversation/index.vue')
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentComponent = shallowRef<DefineComponent | null>(conversationComponent) // 默认加载对话组件
|
||||||
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.app-container {
|
.app-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
Loading…
Reference in New Issue