Merge branch 'feature/im' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/im
commit
82b3276204
|
@ -0,0 +1,27 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export interface ImConversationRespVO {
|
||||
id: number // 编号
|
||||
userId: number // 所属用户
|
||||
conversationType: number // 会话类型
|
||||
targetId: number // 聊天对象编号
|
||||
no: string // 会话标志
|
||||
pinned: boolean // 是否置顶
|
||||
lastReadTime: string // 最后已读时间
|
||||
createTime: string // 创建时间
|
||||
}
|
||||
|
||||
// 获得用户的会话列表
|
||||
export const getConversationList = async () => {
|
||||
return await request.get({ url: `/im/conversation/list` })
|
||||
}
|
||||
|
||||
// 置顶会话
|
||||
export const updatePinned = async (data: any) => {
|
||||
return await request.post({ url: `/im/conversation/update-pinned`, data })
|
||||
}
|
||||
|
||||
// 更新最后已读时间
|
||||
export const updateLastReadTime = async (data: any) => {
|
||||
return await request.post({ url: `/im/conversation/update-last-read-time`, data })
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export interface ImMessageSendReqVO {
|
||||
clientMessageId: string // 客户端消息编号
|
||||
receiverId: number // 接收人编号
|
||||
conversationType: number // 会话类型
|
||||
contentType: number // 内容类型
|
||||
content: string // 内容
|
||||
}
|
||||
|
||||
export interface ImMessageSendRespVO {
|
||||
id: number // 编号
|
||||
sendTime: string // 发送时间
|
||||
}
|
||||
|
||||
export interface ImMessageRespVO {
|
||||
id: number // 编号
|
||||
conversationType: number // 会话类型
|
||||
senderId: number // 发送人编号
|
||||
senderNickname: string // 发送人昵称
|
||||
senderAvatar: string // 发送人头像
|
||||
receiverId: number // 接收人编号
|
||||
contentType: number // 内容类型
|
||||
content: string // 内容
|
||||
sendTime: string // 发送时间
|
||||
sequence: number // 序号
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
export const sendMessage = async (data: ImMessageSendReqVO) => {
|
||||
return await request.post({ url: `/im/message/send`, data })
|
||||
}
|
||||
|
||||
// 消息列表-拉取大于 sequence 的消息列表
|
||||
export const pullMessageList = async (params: { sequence: number; size: number }) => {
|
||||
return await request.get({ url: `/im/message/pull`, params })
|
||||
}
|
||||
|
||||
// 消息列表-根据接收人和发送时间进行分页查询
|
||||
export const getMessageList = async (params: any) => {
|
||||
return await request.get({ url: `/im/message/list`, params })
|
||||
}
|
|
@ -97,7 +97,7 @@ $prefix-cls: #{$namespace}--cropper-avatar;
|
|||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
|
||||
::v-deep(svg) {
|
||||
:deep(svg) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ const querySearch = () => {
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .el-input__wrapper {
|
||||
:deep(.el-input__wrapper) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,20 +8,57 @@ const SESSION_MESSAGE_TYPE = {
|
|||
const CUSTOM_TYPE = {
|
||||
userCard: '个人名片'
|
||||
}
|
||||
// const ALL_MESSAGE_TYPE = {
|
||||
// TEXT: 'txt',
|
||||
// IMAGE: 'img',
|
||||
// AUDIO: 'audio',
|
||||
// LOCAL: 'loc',
|
||||
// VIDEO: 'video',
|
||||
// FILE: 'file',
|
||||
// CUSTOM: 'custom',
|
||||
// CMD: 'cmd',
|
||||
// INFORM: 'inform' //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。
|
||||
// }
|
||||
const ALL_MESSAGE_TYPE = {
|
||||
TEXT: 'txt',
|
||||
IMAGE: 'img',
|
||||
AUDIO: 'audio',
|
||||
LOCAL: 'loc',
|
||||
VIDEO: 'video',
|
||||
FILE: 'file',
|
||||
CUSTOM: 'custom',
|
||||
CMD: 'cmd',
|
||||
INFORM: 'inform' //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。
|
||||
TEXT: 101,
|
||||
IMAGE: 102,
|
||||
AUDIO: 103,
|
||||
VIDEO: 104,
|
||||
FILE: 105,
|
||||
AT_TEXT: 106,
|
||||
MERGE: 107,
|
||||
CARD: 108,
|
||||
LOCATION: 109,
|
||||
CUSTOM: 110,
|
||||
REVOKE_RECEIPT: 111,
|
||||
C2C_RECEIPT: 112,
|
||||
TYPING: 113,
|
||||
QUOTE: 114,
|
||||
FACE: 115,
|
||||
ADVANCED_REVOKE: 118,
|
||||
FRIEND_ADDED: 1201,
|
||||
OA_NOTIFICATION: 1400,
|
||||
GROUP_CREATED: 1501,
|
||||
GROUP_INFO_CHANGED: 1502,
|
||||
MEMBER_QUIT: 1504,
|
||||
GROUP_OWNER_CHANGED: 1507,
|
||||
MEMBER_KICKED: 1508,
|
||||
MEMBER_INVITED: 1509,
|
||||
MEMBER_ENTER: 1510,
|
||||
GROUP_DISMISSED: 1511,
|
||||
GROUP_MEMBER_MUTED: 1512,
|
||||
GROUP_MEMBER_CANCEL_MUTED: 1513,
|
||||
GROUP_MUTED: 1514,
|
||||
GROUP_CANCEL_MUTED: 1515,
|
||||
GROUP_ANNOUNCEMENT_UPDATED: 1519,
|
||||
GROUP_NAME_UPDATED: 1520,
|
||||
BURN_CHANGE: 1701,
|
||||
REVOKE: 2101
|
||||
}
|
||||
const CHAT_TYPE = {
|
||||
SINGLE: 'singleChat',
|
||||
GROUP: 'groupChat'
|
||||
SINGLE: 1,
|
||||
GROUP: 3,
|
||||
NOTIFICATION: 4
|
||||
}
|
||||
|
||||
const MENTION_ALL = {
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { ref } from 'vue'
|
||||
import IM from '@/views/im/index.vue'
|
||||
import { useRouter } from 'vue-router' // 导入 useRouter 方法
|
||||
import { shallowRef, defineAsyncComponent, DefineComponent } from 'vue'
|
||||
|
||||
defineOptions({ name: 'ImChat' })
|
||||
// 异步加载可能的对话框内容组件
|
||||
const IMComponent = defineAsyncComponent(() => import('@/views/im/index.vue'))
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const router = useRouter() // 创建 router 实例
|
||||
const dialogVisible = shallowRef(false)
|
||||
const currentComponent = shallowRef<DefineComponent | null>(null)
|
||||
|
||||
// 添加点击事件处理函数
|
||||
function handleClick() {
|
||||
dialogVisible.value = !dialogVisible.value
|
||||
router.push('/im/conversation') // 设置路由为 /im/conversation
|
||||
// 添加点击事件处理函数,显示对话框并加载 IM 组件
|
||||
function openDialog() {
|
||||
dialogVisible.value = true
|
||||
currentComponent.value = IMComponent // 加载 IM 组件
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-hover" v-bind="$attrs">
|
||||
<div class="custom-hover" v-bind="$attrs" @click="openDialog">
|
||||
<ElBadge>
|
||||
<Icon :size="18" class="cursor-pointer" icon="ep:chat-round" @click="handleClick" />
|
||||
<Icon :size="18" class="cursor-pointer" icon="ep:chat-round" />
|
||||
</ElBadge>
|
||||
</div>
|
||||
<Dialog v-model="dialogVisible" width="90%" top="10vh">
|
||||
<IM />
|
||||
<component :is="currentComponent" />
|
||||
<!-- 使用动态组件 -->
|
||||
</Dialog>
|
||||
</template>
|
||||
|
|
|
@ -591,18 +591,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
},
|
||||
component: () => import('@/views/im/Conversation/index.vue'),
|
||||
children: [
|
||||
{
|
||||
// 会话详情
|
||||
path: 'informDetails',
|
||||
name: 'InformDetails',
|
||||
meta: {
|
||||
title: '通知详情',
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
noTagsView: true
|
||||
},
|
||||
component: () => import('@/views/im/InformDetails/index.vue')
|
||||
},
|
||||
{
|
||||
//聊天对话框
|
||||
path: 'message',
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// fileSizeFormat.ts
|
||||
export default function fileSizeFormat(value: number): string {
|
||||
const s = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']
|
||||
const e = Math.floor(Math.log(value) / Math.log(1024))
|
||||
return (value / Math.pow(1024, Math.floor(e))).toFixed(2) + ' ' + s[e]
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
interface ParsedLinkResult {
|
||||
isLink: boolean
|
||||
msg: string
|
||||
}
|
||||
|
||||
const paseLink = (msg: string): ParsedLinkResult => {
|
||||
let isLink = false
|
||||
const reg =
|
||||
/(https?\:\/\/|www\.)([a-zA-Z0-9-]+(\.[a-zA-Z0-9]+)+)(\:[0-9]{2,4})?\/?((\.[:_0-9a-zA-Z-]+)|[:_0-9a-zA-Z-]*\/?)*\??[:_#@*&%0-9a-zA-Z-/=]*/gm
|
||||
|
||||
msg = msg.replace(reg, function (v: string): string {
|
||||
const prefix = /^https?/gm.test(v)
|
||||
isLink = prefix
|
||||
return "<a href='" + (prefix ? v : '//' + v) + "' target='_blank'>" + v + '</a>'
|
||||
})
|
||||
|
||||
return { isLink, msg }
|
||||
}
|
||||
|
||||
export default paseLink
|
|
@ -1,26 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { reactive } from 'vue'
|
||||
/* 头像相关 */
|
||||
import informIcon from '@/assets/imgs/im/avatar/inform.png'
|
||||
import * as ConversationApi from '@/api/im/conversation'
|
||||
/* route */
|
||||
const route = useRoute()
|
||||
/* router */
|
||||
const router = useRouter()
|
||||
//取系统通知数据
|
||||
const informDetail = computed(() => {
|
||||
const informDetailArr = reactive([
|
||||
{
|
||||
from: '系统通知',
|
||||
desc: '您有一条新的通知',
|
||||
time: new Date(),
|
||||
untreated: 1
|
||||
}
|
||||
])
|
||||
const lastInformDeatail = informDetailArr[0] || {}
|
||||
const untreated = 1
|
||||
return { untreated, lastInformDeatail }
|
||||
})
|
||||
|
||||
//取好友列表(主要使用好友下的用户属性相关)
|
||||
const friendList = reactive({
|
||||
|
@ -40,10 +25,22 @@ const conversationList = reactive([
|
|||
msg: 'hello word!'
|
||||
},
|
||||
latestSendTime: new Date(),
|
||||
unreadMessageNum: 5,
|
||||
isMention: false
|
||||
unreadMessageNum: 5
|
||||
}
|
||||
])
|
||||
// let conversationList = reactive({})
|
||||
// // 获得用户的会话列表
|
||||
// const getConversationList = async () => {
|
||||
// const res = await ConversationApi.getConversationList()
|
||||
// if (res.code === 200) {
|
||||
// console.log('会话列表', res.data)
|
||||
// conversationList = res.data
|
||||
// }
|
||||
// }
|
||||
// // 初始化
|
||||
// onMounted(() => {
|
||||
// getConversationList()
|
||||
// })
|
||||
|
||||
//处理会话name
|
||||
const handleConversationName = computed(() => {
|
||||
|
@ -53,7 +50,7 @@ const handleConversationName = computed(() => {
|
|||
const handleLastMsgNickName = computed(() => {
|
||||
return ''
|
||||
})
|
||||
const emit = defineEmits(['toInformDetails', 'toChatMessage'])
|
||||
const emit = defineEmits(['toChatMessage'])
|
||||
//普通会话
|
||||
const checkedConverItemIndex = ref(null)
|
||||
const toChatMessage = (item, itemKey, index) => {
|
||||
|
@ -69,36 +66,6 @@ const deleteConversation = (itemKey) => {
|
|||
</script>
|
||||
<template>
|
||||
<el-scrollbar class="session_list" style="overflow: auto" tag="ul">
|
||||
<!-- 系统通知会话 -->
|
||||
<li
|
||||
v-if="JSON.stringify(informDetail.lastInformDeatail) !== '{}' && informDetail.untreated >= 1"
|
||||
class="session_list_item"
|
||||
@click="$emit('toInformDetails')"
|
||||
>
|
||||
<div class="item_body item_left">
|
||||
<!-- 通知头像 -->
|
||||
<div class="session_other_avatar">
|
||||
<el-avatar :size="34" :src="informIcon" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_main">
|
||||
<div class="name">系统通知</div>
|
||||
<div class="last_msg_body">
|
||||
{{ informDetail.lastInformDeatail.from }}:{{ informDetail.lastInformDeatail.desc }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_right">
|
||||
<span class="time">{{
|
||||
formatDate(informDetail.lastInformDeatail.time, 'MM/DD/HH:mm')
|
||||
}}</span>
|
||||
<span class="unReadNum_box" v-if="informDetail.untreated >= 1">
|
||||
<sup
|
||||
class="unReadNum_count"
|
||||
v-text="informDetail.untreated >= 99 ? '99+' : informDetail.untreated"
|
||||
></sup>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<!-- 普通会话 -->
|
||||
<template v-if="Object.keys(conversationList).length > 0">
|
||||
<li
|
||||
|
@ -133,7 +100,6 @@ const deleteConversation = (itemKey) => {
|
|||
<div class="item_body item_main">
|
||||
<div class="name"> 好友 </div>
|
||||
<div class="last_msg_body">
|
||||
<span class="last_msg_body_mention" v-if="item.isMention">[有人@我]</span>
|
||||
<span v-show="item.conversationType === 2">好友</span>
|
||||
{{ item.latestMessage.msg }}
|
||||
</div>
|
||||
|
|
|
@ -1,40 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
/* 搜索框组件 */
|
||||
import { defineAsyncComponent, shallowRef } from 'vue'
|
||||
import { SearchInput } from '@/components/Im/SearchInput'
|
||||
/* 欢迎页 */
|
||||
import { Welcome } from '@/components/Im/Welcome'
|
||||
import ConversationList from '../Conversation/components/ConversationList.vue'
|
||||
import router from '@/router'
|
||||
import { Welcome } from '@/components/Im/Welcome'
|
||||
|
||||
//路由跳转-系统通知
|
||||
const toInformDetails = () => {
|
||||
router.push('/im/conversation/informDetails')
|
||||
}
|
||||
const MessageComponent = defineAsyncComponent(() => import('@/views/im/Message/index.vue'))
|
||||
|
||||
//路由跳转-对应好友会话
|
||||
const toChatMessage = (id, chatType) => {
|
||||
const currentComponent = shallowRef(Welcome) // 默认加载欢迎页组件
|
||||
|
||||
const toChatMessage = (id) => {
|
||||
console.log('>>>>>>>id', id)
|
||||
router.push({
|
||||
path: '/im/conversation/message',
|
||||
query: {
|
||||
id,
|
||||
chatType
|
||||
}
|
||||
})
|
||||
currentComponent.value = MessageComponent // 加载消息组件
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container style="height: 100%">
|
||||
<el-aside class="chat_conversation_box">
|
||||
<!-- 搜索组件 -->
|
||||
<SearchInput :searchType="'conversation'" />
|
||||
<div class="chat_conversation_list">
|
||||
<ConversationList @to-inform-details="toInformDetails" @to-chat-message="toChatMessage" />
|
||||
<ConversationList @to-chat-message="toChatMessage" />
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main class="chat_conversation_main_box">
|
||||
<router-view />
|
||||
<Welcome />
|
||||
<component :is="currentComponent" />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<h2>系统通知</h2>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -55,7 +55,7 @@
|
|||
}
|
||||
|
||||
/* loading svg大小调整 */
|
||||
::v-deep .circular {
|
||||
:deep(.circular) {
|
||||
margin-top: 8px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
|
|
|
@ -7,6 +7,8 @@ import { onClickOutside } from '@vueuse/core'
|
|||
/* 组件 */
|
||||
import PreviewSendImg from '../suit/previewSendImg.vue'
|
||||
import VueAt from 'vue-at/dist/vue-at-textarea' // for textarea
|
||||
import * as MessageApi from '@/api/im/message'
|
||||
import { generateUUID } from '@/utils'
|
||||
const props = defineProps({
|
||||
nowPickInfo: {
|
||||
type: Object,
|
||||
|
@ -100,17 +102,21 @@ const sendTextMessage = _.debounce(async () => {
|
|||
em_at_list: isAtAll.value ? MENTION_ALL.VALUE : _.map(atMembers.value, 'value')
|
||||
}
|
||||
}
|
||||
const imMessageSendReqVO = {
|
||||
clientMessageId: generateUUID(),
|
||||
receiverId: nowPickInfo.value.id,
|
||||
conversationType: nowPickInfo.value.chatType,
|
||||
contentType: ALL_MESSAGE_TYPE.TEXT,
|
||||
content: textContent.value
|
||||
}
|
||||
//关闭引用框
|
||||
if (messageQuoteRef.value?.isShowQuoteMsgBox) {
|
||||
}
|
||||
textContent.value = ''
|
||||
messageQuoteRef.value?.clearQuoteContent()
|
||||
try {
|
||||
console.log('msgOptions', msgOptions)
|
||||
// await store.dispatch('sendShowTypeMessage', {
|
||||
// msgType: ALL_MESSAGE_TYPE.TEXT,
|
||||
// msgOptions
|
||||
// })
|
||||
console.log('imMessageSendReqVO', imMessageSendReqVO)
|
||||
await MessageApi.sendMessage(imMessageSendReqVO)
|
||||
} catch (error) {
|
||||
//handleSDKErrorNotifi(error.type, error.message)
|
||||
console.log('>>>>>>>发送失败+++++++', error)
|
||||
|
|
|
@ -2,60 +2,37 @@
|
|||
import { formatDate } from '@/utils/formatTime'
|
||||
/* 默认头像 */
|
||||
import defaultAvatar from '@/assets/imgs/avatar.gif'
|
||||
/* emits */
|
||||
const emit = defineEmits(['scroll-message-list', 're-edit-message', 'message-quote'])
|
||||
const messageData = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'text',
|
||||
isRecall: false,
|
||||
time: '2024-04-01 12:00:00',
|
||||
from: '1',
|
||||
msg: 'Hello, world!',
|
||||
modifiedInfo: {
|
||||
operationCount: 1
|
||||
}
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import avatarImg from '@/assets/imgs/avatar.gif'
|
||||
import paseLink from '@/utils/paseLink.ts'
|
||||
import fileSizeFormat from '@/utils/fileSizeFormat'
|
||||
import { messageType } from '@/constant/im'
|
||||
const { ALL_MESSAGE_TYPE, CUSTOM_TYPE } = messageType
|
||||
// 当前用户信息
|
||||
const userStore = useUserStore()
|
||||
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
||||
/* props */
|
||||
const props = defineProps({
|
||||
messageData: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'text',
|
||||
isRecall: false,
|
||||
time: '2024-04-01 12:00:01',
|
||||
from: '2',
|
||||
msg: 'Hi, there!',
|
||||
modifiedInfo: {
|
||||
operationCount: 0
|
||||
nowPickInfo: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'text',
|
||||
isRecall: true,
|
||||
time: '2024-04-01 12:00:02',
|
||||
from: '1',
|
||||
msg: 'Hello, world!',
|
||||
modifiedInfo: {
|
||||
operationCount: 0
|
||||
}
|
||||
}
|
||||
])
|
||||
const ALL_MESSAGE_TYPE = {
|
||||
TEXT: 'txt',
|
||||
IMAGE: 'img',
|
||||
AUDIO: 'audio',
|
||||
LOCAL: 'loc',
|
||||
VIDEO: 'video',
|
||||
FILE: 'file',
|
||||
CUSTOM: 'custom',
|
||||
CMD: 'cmd',
|
||||
INFORM: 'inform' //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。
|
||||
}
|
||||
})
|
||||
const { nowPickInfo } = toRefs(props)
|
||||
const { messageData } = toRefs(props)
|
||||
/* 处理时间显示间隔 */
|
||||
const handleMsgTimeShow = (time, index) => {
|
||||
console.log('>>>>>时间显示', time, index)
|
||||
const msgList = Array.from(messageData.value)
|
||||
if (index !== 0) {
|
||||
const lastTime = msgList[index - 1].time
|
||||
return time - lastTime > 50000 ? formatDate(time, 'MM/DD/HH:mm') : false
|
||||
console.log('>>>>>时间间隔', time - lastTime, time, lastTime)
|
||||
return time - lastTime > 50000 ? formatDate(time, 'MM/DD/HH:mm') : ''
|
||||
} else {
|
||||
return formatDate(time, 'MM/DD/HH:mm')
|
||||
}
|
||||
|
@ -64,9 +41,15 @@ const handleMsgTimeShow = (time, index) => {
|
|||
const isMyself = (msgBody) => {
|
||||
return msgBody.from === '1'
|
||||
}
|
||||
/* 文本中是否包含link */
|
||||
const isLink = computed(() => {
|
||||
return (msg) => {
|
||||
return paseLink(msg).isLink
|
||||
}
|
||||
})
|
||||
/* 获取自己的用户信息 */
|
||||
const loginUserInfo = {
|
||||
avatarurl: 'https://avatars.githubusercontent.com/u/1?v=4'
|
||||
avatarurl: avatar.value
|
||||
}
|
||||
/* 获取他人的用户信息 */
|
||||
const otherUserInfo = (from) => {
|
||||
|
@ -108,10 +91,6 @@ const startplayAudio = (msgBody) => {
|
|||
audioPlayStatus.playMsgId = ''
|
||||
})
|
||||
}
|
||||
//父组件重新编辑方法
|
||||
const reEdit = (msg) => emit('reEditMessage', msg)
|
||||
//调用父组件引用消息
|
||||
const onMsgQuote = (msg) => emit('messageQuote', msg)
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
|
@ -123,14 +102,14 @@ const onMsgQuote = (msg) => emit('messageQuote', msg)
|
|||
>
|
||||
<!-- 普通消息气泡 -->
|
||||
<div
|
||||
v-if="!msgBody.isRecall && msgBody.type !== ALL_MESSAGE_TYPE.INFORM"
|
||||
v-if="!msgBody.isRecall && msgBody.type !== ALL_MESSAGE_TYPE.OA_NOTIFICATION"
|
||||
class="message_box_item"
|
||||
:style="{
|
||||
flexDirection: isMyself(msgBody) ? 'row-reverse' : 'row'
|
||||
}"
|
||||
>
|
||||
<div class="message_item_time">
|
||||
{{ handleMsgTimeShow(msgBody.time, index) || '' }}
|
||||
{{ handleMsgTimeShow(msgBody.time, index) }}
|
||||
</div>
|
||||
<el-avatar
|
||||
class="message_item_avator"
|
||||
|
@ -161,12 +140,6 @@ const onMsgQuote = (msg) => emit('messageQuote', msg)
|
|||
>
|
||||
<template v-if="!isLink(msgBody.msg)">
|
||||
{{ msgBody.msg }}
|
||||
<!-- 已编辑 -->
|
||||
<sup
|
||||
style="font-size: 7px; color: #707784"
|
||||
v-show="msgBody?.modifiedInfo?.operationCount"
|
||||
>(已编辑)</sup
|
||||
>
|
||||
</template>
|
||||
<template v-else> <span v-html="paseLink(msgBody.msg).msg"> </span></template>
|
||||
</p>
|
||||
|
@ -244,62 +217,10 @@ const onMsgQuote = (msg) => emit('messageQuote', msg)
|
|||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 右键点击弹起更多功能栏 -->
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT && isSupported"
|
||||
@click="copyTextMessages(msgBody.msg)"
|
||||
>
|
||||
复制
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="isMyself(msgBody)" @click="recallMessage(msgBody)">
|
||||
撤回
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT && isMyself(msgBody)"
|
||||
@click="showModifyMsgModal(msgBody)"
|
||||
>
|
||||
编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="onMsgQuote(msgBody)"> 引用 </el-dropdown-item>
|
||||
<el-dropdown-item @click="deleteMessage(msgBody)"> 删除 </el-dropdown-item>
|
||||
<el-dropdown-item v-if="!isMyself(msgBody)" @click="informOnMessage(msgBody)">
|
||||
举报
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 引用消息展示框 -->
|
||||
<div
|
||||
class="message_quote_box"
|
||||
v-if="msgBody?.ext?.msgQuote"
|
||||
@click="clickQuoteMessage(msgBody.ext.msgQuote)"
|
||||
>
|
||||
<p>
|
||||
{{ msgBody?.ext?.msgQuote?.msgSender }}:{{ msgBody?.ext?.msgQuote?.msgPreview }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 撤回消息通知通知 -->
|
||||
<div v-if="msgBody.isRecall" class="recall_style">
|
||||
{{ isMyself(msgBody) ? '你' : `${msgBody.from}` }}撤回了一条消息<span
|
||||
class="reEdit"
|
||||
v-show="isMyself(msgBody) && msgBody.type === ALL_MESSAGE_TYPE.TEXT"
|
||||
@click="reEdit(msgBody.msg)"
|
||||
>重新编辑</span
|
||||
>
|
||||
</div>
|
||||
<!-- 灰色系统通知 -->
|
||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.INFORM" class="inform_style">
|
||||
<p>
|
||||
{{ msgBody.msg }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ReportMessage ref="reportMessage" />
|
||||
<ModifyMessage ref="modifyMessageRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
border-radius: 0 0 3px 0;
|
||||
}
|
||||
|
||||
::v-deep .el-drawer {
|
||||
:deep(.el-drawer) {
|
||||
margin-top: 60px;
|
||||
width: 150px;
|
||||
height: calc(100% - 60px);
|
||||
|
|
|
@ -4,11 +4,11 @@ import { messageType } from '@/constant/im'
|
|||
/* 组件 */
|
||||
import MessageList from './components/messageList/index.vue'
|
||||
import InputBox from './components/inputBox/index.vue'
|
||||
import * as MessageApi from '@/api/im/message'
|
||||
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const { CHAT_TYPE } = messageType
|
||||
const { CHAT_TYPE, ALL_MESSAGE_TYPE } = messageType
|
||||
/* header 操作 */
|
||||
const drawer = ref(false) //抽屉显隐
|
||||
const handleDrawer = () => {
|
||||
|
@ -25,10 +25,10 @@ const delTheFriend = () => {
|
|||
}
|
||||
// 当前聊天对象信息
|
||||
const nowPickInfo = ref({
|
||||
id: '1',
|
||||
id: 1,
|
||||
chatType: CHAT_TYPE.SINGLE,
|
||||
userInfo: {
|
||||
nickname: '好友1',
|
||||
nickname: '芋道源码',
|
||||
userStatus: '1'
|
||||
},
|
||||
groupDetail: {
|
||||
|
@ -45,27 +45,76 @@ const groupDetail = computed(() => {
|
|||
//获取其id对应的消息内容
|
||||
const messageData = computed(() => [
|
||||
{
|
||||
type: 'text'
|
||||
}
|
||||
])
|
||||
//监听路由改变获取对应的getIdInfo
|
||||
const stopWatchRoute = watch(
|
||||
() => query,
|
||||
(routeVal) => {
|
||||
console.log('>>>>>>>>监听到路由参数变化', routeVal)
|
||||
if (routeVal) {
|
||||
// nowPickInfo.value = { ...routeVal }
|
||||
// loginState.value && getIdInfo(routeVal)
|
||||
id: 1,
|
||||
type: ALL_MESSAGE_TYPE.TEXT,
|
||||
isRecall: false,
|
||||
time: '1711944110000',
|
||||
from: '1',
|
||||
msg: 'Hello, world!111',
|
||||
modifiedInfo: {
|
||||
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
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
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'
|
||||
}
|
||||
)
|
||||
//离开该路由销毁route监听
|
||||
onBeforeRouteLeave(() => {
|
||||
stopWatchRoute()
|
||||
})
|
||||
},
|
||||
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 isMoreHistoryMsg = ref(true) //加载文案展示为加载更多还是已无更多。
|
||||
|
@ -74,6 +123,10 @@ const notScrollBottom = ref(false) //是否滚动置底
|
|||
const fechHistoryMessage = (loadType) => {
|
||||
console.log(loadType)
|
||||
console.log('加载更多')
|
||||
loadingHistoryMsg.value = true
|
||||
setTimeout(() => {
|
||||
loadingHistoryMsg.value = false
|
||||
}, 1000)
|
||||
}
|
||||
//控制消息滚动
|
||||
const scrollMessageList = (direction) => {
|
||||
|
@ -96,39 +149,12 @@ const messageQuote = (msg) => inputBox.value.handleQuoteMessage(msg)
|
|||
</div>
|
||||
<div v-else> {{ nowPickInfo.id }}<span style="font-size: 10px">(非好友)</span> </div>
|
||||
</template>
|
||||
<!-- 单人展示删除拉黑 -->
|
||||
<span class="more" v-if="nowPickInfo.chatType === CHAT_TYPE.SINGLE">
|
||||
<el-dropdown placement="bottom-end" trigger="click">
|
||||
<svg
|
||||
width="18"
|
||||
height="4"
|
||||
viewBox="0 0 18 4"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="2" cy="2" r="2" fill="#333333" />
|
||||
<circle cx="9" cy="2" r="2" fill="#333333" />
|
||||
<circle cx="16" cy="2" r="2" fill="#333333" />
|
||||
</svg>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="delTheFriend"> 删除好友 </el-dropdown-item>
|
||||
<!-- <el-dropdown-item @click="addFriendToBlackList">
|
||||
加入黑名单
|
||||
</el-dropdown-item> -->
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</el-header>
|
||||
<el-main class="chat_message_main">
|
||||
<el-scrollbar class="main_container" ref="messageContainer">
|
||||
<div class="innerRef">
|
||||
<div v-show="isMoreHistoryMsg" class="chat_message_tips">
|
||||
<div
|
||||
v-show="messageData?.length && messageData[0].type !== 'inform'"
|
||||
class="load_more_msg"
|
||||
>
|
||||
<div v-show="messageData?.length" class="load_more_msg">
|
||||
<el-link
|
||||
v-show="!loadingHistoryMsg"
|
||||
:disabled="!isMoreHistoryMsg"
|
||||
|
@ -140,13 +166,7 @@ const messageQuote = (msg) => inputBox.value.handleQuoteMessage(msg)
|
|||
<el-link v-show="loadingHistoryMsg" disabled>消息加载中...</el-link>
|
||||
</div>
|
||||
</div>
|
||||
<MessageList
|
||||
:nowPickInfo="nowPickInfo"
|
||||
:messageData="messageData"
|
||||
@scroll-message-list="scrollMessageList"
|
||||
@re-edit-message="reEditMessage"
|
||||
@message-quote="messageQuote"
|
||||
/>
|
||||
<MessageList :nowPickInfo="nowPickInfo" :messageData="messageData" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
|
|
|
@ -250,36 +250,36 @@ watch(
|
|||
}
|
||||
|
||||
.components {
|
||||
::v-deep .edit_userinfo_diglog {
|
||||
:deep(.edit_userinfo_diglog) {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::v-deep .setting_func_diglog > .el-dialog__body {
|
||||
.setting_func_diglog :deep(.el-dialog__body) {
|
||||
padding: 28px 24px 24px 24px;
|
||||
}
|
||||
|
||||
::v-deep .setting_func_diglog > .el-dialog__header {
|
||||
.setting_func_diglog :deep(.el-dialog__header) {
|
||||
background: #f2f2f2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::v-deep .edit_userinfo_diglog > .el-dialog__header {
|
||||
.edit_userinfo_diglog :deep(.el-dialog__header) {
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
::v-deep .edit_userinfo_diglog > .el-dialog__body {
|
||||
.edit_userinfo_diglog :deep(.el-dialog__body) {
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::v-deep .login_diglog > .el-dialog__header {
|
||||
.login_diglog :deep(.el-dialog__header) {
|
||||
background: #f2f2f2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::v-deep .personal_setting_card > .el-dialog__header {
|
||||
.personal_setting_card :deep(.el-dialog__header) {
|
||||
background: #f2f2f2;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
<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>
|
||||
<div class="app-container">
|
||||
<el-container class="chat_container">
|
||||
|
@ -10,11 +19,12 @@ defineOptions({ name: 'IM' })
|
|||
<NavBar />
|
||||
</el-aside>
|
||||
<el-main class="chat_main_box">
|
||||
<router-view />
|
||||
<component :is="currentComponent" />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
position: fixed;
|
||||
|
|
Loading…
Reference in New Issue