Files
open-anylink-web/src/views/message/MessageLayout.vue

525 lines
14 KiB
Vue
Raw Normal View History

2024-09-03 17:57:54 +08:00
<!-- eslint-disable prettier/prettier -->
<script setup>
2024-09-14 18:03:40 +08:00
import { ref, onMounted, computed, watch, nextTick } from 'vue'
2024-09-05 10:49:44 +08:00
import {
Phone,
VideoCamera,
Position,
CirclePlus,
Setting,
LocationInformation,
Clock,
Picture,
FolderAdd,
CreditCard,
PictureRounded
} from '@element-plus/icons-vue'
import DragLine from '@/components/common/DragLine.vue'
import SearchBox from '@/components/common/SearchBox.vue'
import AddBotton from '@/components/common/AddBotton.vue'
import SessionBox from '@/components/message/SessionBox.vue'
2024-09-05 10:49:44 +08:00
import InputTool from '@/components/message/InputTool.vue'
import InputEditor from '@/components/message/InputEditor.vue'
2024-09-08 23:49:43 +08:00
import MessageItem from '@/components/message/MessageItem.vue'
import { userStore, settingStore, messageStore } from '@/stores'
2024-09-05 10:49:44 +08:00
import backgroupImage from '@/assets/messagebx_bg.webp'
import { msgChatSessionListService, msgChatPullMsgService } from '@/api/message'
2024-09-14 22:36:16 +08:00
import { MsgType } from '@/proto/msg'
import wsConnect from '@/js/websocket/wsConnect'
2024-08-23 23:05:57 +08:00
const userData = userStore()
2024-09-02 13:59:02 +08:00
const settingData = settingStore()
2024-09-05 10:49:44 +08:00
const messageData = messageStore()
const asideWidth = ref(0)
2024-09-05 10:49:44 +08:00
const asideWidthMin = 200
const asideWidthMax = 500
const inputBoxHeight = ref(0)
const inputBoxHeightMin = 200
const inputBoxHeightMax = 500
2024-09-05 10:49:44 +08:00
const isShowTopLoading = ref(true)
2024-09-08 23:49:43 +08:00
const isTopLoading = ref(false)
const loadMoreTips = ref('查看更多消息')
const loadCursor = computed(() => {
return isTopLoading.value ? 'auto' : 'pointer'
})
const choosedSession = computed(() => {
if (userData.lastSessionId)
return messageData.sessionList[userData.lastSessionId]
else
return {}
})
const msgRecords = ref([])
2024-09-14 22:36:16 +08:00
const msgListDiv = ref()
2024-09-14 18:03:40 +08:00
2024-09-05 17:41:27 +08:00
onMounted(async () => {
asideWidth.value = settingData.sessionListDrag[userData.user.account] || 300
inputBoxHeight.value = settingData.inputBoxDrag[userData.user.account] || 300
2024-09-05 17:41:27 +08:00
const res = await msgChatSessionListService()
messageData.setSessionList(res.data.data) //入缓存
if (userData.lastSessionId) {
pullMsg()
}
if (msgListDiv.value) {
// 首次进到消息页面,不会有有值
msgListDiv.value.scrollTop = msgListDiv.value.scrollHeight
}
})
2024-09-14 18:03:40 +08:00
// 把sessionList转成数组并按照lastMsgTime排序
const sessionListSorted = computed(() => {
if (!messageData.sessionList) {
return []
}
else {
let sessionArr = Object.values(messageData.sessionList)
return sessionArr.sort((a, b) => b.lastMsgTime - a.lastMsgTime)
}
2024-09-05 10:49:44 +08:00
})
2024-09-05 23:02:44 +08:00
const showName = computed(() => {
switch (choosedSession.value?.sessionType) {
case MsgType.CHAT:
return choosedSession.value.objectInfo.nickName
case MsgType.GROUP_CHAT:
return choosedSession.value.objectInfo.groupName
2024-09-05 23:02:44 +08:00
default:
return ''
}
})
const showId = computed(() => {
switch (choosedSession.value?.sessionType) {
case MsgType.CHAT:
return choosedSession.value.objectInfo.account
case MsgType.GROUP_CHAT:
return choosedSession.value.objectInfo.groupId
2024-09-05 23:02:44 +08:00
default:
return ''
}
})
const getLastMsgTime = (index) => {
if (index > 0) {
return msgRecords.value[index - 1].msgTime;
} else {
return null;
}
}
2024-09-05 10:49:44 +08:00
const onAsideDragUpdate = ({ width }) => {
asideWidth.value = width
settingData.setSessionListDrag({
...settingData.sessionListDrag,
[userData.user.account]: width
})
}
2024-09-03 17:57:54 +08:00
2024-09-05 10:49:44 +08:00
const onInputBoxDragUpdate = ({ height }) => {
inputBoxHeight.value = height
msgListReachBottom()
settingData.setInputBoxDrag({
...settingData.inputBoxDrag,
[userData.user.account]: height
})
2024-09-05 10:49:44 +08:00
}
const pullMsg = () => {
msgChatPullMsgService({
sessionId: userData.lastSessionId,
readMsgId: choosedSession.value.readMsgId,
readTime: choosedSession.value.readTime,
pageSize: 20
})
.then((res) => {
msgRecords.value = res.data.data.msgList
messageData.updateSession({
sessionId: userData.lastSessionId,
readMsgId: res.data.data.lastMsgId,
readTime: new Date(),
lastMsgId: res.data.data.lastMsgId,
lastMsgContent: res.data.data.msgList.content,
lastMsgTime: res.data.data.msgList.msgTime,
unreadCount: 0
})
})
}
// 表示有个session被选中了
const handleIsChoosed = (exportSession) => {
userData.setLastSessionId(exportSession.sessionId)
pullMsg()
}
2024-09-05 23:02:44 +08:00
const handleSwitchTag = (obj) => {
messageData.updateSession(obj)
2024-09-05 10:49:44 +08:00
}
2024-09-14 18:03:40 +08:00
const handleExportContent = (content) => {
// TODO 这里还要考虑失败情况1消息发不出去2消息发出去了服务器不发“已发送”
wsConnect.sendMsg(showId.value, choosedSession.value.sessionType, content, (deliveredMsg) => {
2024-09-18 23:54:57 +08:00
const now = new Date()
messageData.updateSession({
sessionId: userData.lastSessionId,
readMsgId: deliveredMsg.body.msgId, // 发消息视为已经读到最后一条消息(自己发的)
2024-09-18 23:54:57 +08:00
readTime: now,
lastMsgId: deliveredMsg.body.msgId, // lastMsgId = 最后一条消息(自己发的)
2024-09-18 23:54:57 +08:00
lastMsgContent: content,
lastMsgTime: now,
unreadCount: 0, // readMsgId = lastMsgId = 最后一条消息自己发的因此未读是0
draft: '' //草稿意味着要清空
})
2024-09-18 23:54:57 +08:00
// 如果当前sessionid和这个“已发送”消息的sessionId更新到msgRecords中
if (userData.lastSessionId === deliveredMsg.body.sessionId) {
2024-09-18 23:54:57 +08:00
msgRecords.value.push({
sessionId: userData.lastSessionId,
2024-09-18 23:54:57 +08:00
msgId: deliveredMsg.body.msgId,
fromId: userData.user.account,
msgType: choosedSession.value.sessionType,
content: content,
msgTime: now
})
}
2024-09-14 22:36:16 +08:00
})
2024-09-14 18:03:40 +08:00
}
2024-09-08 23:49:43 +08:00
const onLoadMore = () => {
isTopLoading.value = true
loadMoreTips.value = ''
}
watch(msgRecords, () => {
if (msgRecords.value) {
msgRecords.value = msgRecords.value.sort((a, b) => a.msgId - b.msgId)
}
msgListReachBottom()
}, {deep: true})
const msgListReachBottom = () => {
2024-09-14 18:03:40 +08:00
nextTick(() => {
2024-09-14 22:36:16 +08:00
msgListDiv.value.scrollTo({
top: msgListDiv.value.scrollHeight,
2024-09-14 18:03:40 +08:00
behavior: 'smooth'
})
})
}
2024-09-14 18:03:40 +08:00
</script>
<template>
<el-container class="msg-container-hole">
2024-09-02 13:59:02 +08:00
<el-aside class="msg-aside bdr-r" :style="{ width: asideWidth + 'px' }">
<div class="msg-aside-main">
2024-09-05 10:49:44 +08:00
<div class="header">
<SearchBox></SearchBox>
<AddBotton></AddBotton>
</div>
2024-09-14 14:15:00 +08:00
<div class="session-list my-scrollbar">
2024-09-05 10:49:44 +08:00
<SessionBox
v-for="item in sessionListSorted"
:key="item.sessionId"
:sesionInfo="item"
2024-09-18 21:33:01 +08:00
@isChoosed="handleIsChoosed"
@switchTag="handleSwitchTag"
2024-09-05 10:49:44 +08:00
></SessionBox>
</div>
</div>
<DragLine
direction="right"
2024-09-05 10:49:44 +08:00
:min="asideWidthMin"
:max="asideWidthMax"
:origin-size="asideWidth"
2024-09-05 10:49:44 +08:00
@drag-update="onAsideDragUpdate"
></DragLine>
</el-aside>
2024-09-05 10:49:44 +08:00
<el-main class="msg-box">
<el-image
class="backgroup-image"
v-if="!userData.lastSessionId"
2024-09-05 10:49:44 +08:00
:src="backgroupImage"
fit="cover"
></el-image>
<el-container v-else class="container">
2024-09-05 10:49:44 +08:00
<el-header class="header bdr-b">
2024-09-05 23:02:44 +08:00
<div class="show-name-id">
<span class="show-name">{{ showName }}</span>
<span v-if="choosedSession?.sessionType === MsgType.CHAT" class="show-id">{{
showId
}}</span>
2024-09-05 23:02:44 +08:00
</div>
2024-09-05 10:49:44 +08:00
<div class="action-set">
<el-button class="action-button" :icon="Phone" circle />
<el-button class="action-button" :icon="VideoCamera" circle />
<el-button class="action-button" :icon="Position" circle />
<el-button class="action-button" :icon="CirclePlus" circle />
<el-button class="action-button" :icon="Setting" circle />
</div>
</el-header>
<el-main class="body">
<div v-if="isShowTopLoading" class="top-loading">
<div
v-loading="isTopLoading"
:fullscreen="false"
class="loading-box"
@click="onLoadMore"
:style="{ cursor: loadCursor }"
>
{{ loadMoreTips }}
2024-09-08 23:49:43 +08:00
</div>
</div>
2024-09-14 22:36:16 +08:00
<div class="show-box my-scrollbar" ref="msgListDiv">
2024-09-08 23:49:43 +08:00
<div class="message-main">
2024-09-09 16:45:30 +08:00
<span class="no-more-message">当前无更多消息</span>
<MessageItem
v-for="(item, index) in msgRecords"
:key="index"
2024-09-14 22:36:16 +08:00
:msg="item"
:obj="choosedSession?.objectInfo"
:lastMsgTime="getLastMsgTime(index)"
></MessageItem>
2024-09-08 23:49:43 +08:00
</div>
</div>
2024-09-05 10:49:44 +08:00
<div class="input-box bdr-t" :style="{ height: inputBoxHeight + 'px' }">
<el-container class="input-box-container">
<el-header class="input-box-header">
<DragLine
direction="top"
:min="inputBoxHeightMin"
:max="inputBoxHeightMax"
:origin-size="inputBoxHeight"
@drag-update="onInputBoxDragUpdate"
></DragLine>
<div class="tool-set">
<div class="left-tools">
<InputTool tips="表情">
<template #iconSlot>
<PictureRounded />
</template>
</InputTool>
<InputTool tips="图片">
<template #iconSlot>
<Picture />
</template>
</InputTool>
<InputTool tips="文件">
<template #iconSlot>
<FolderAdd />
</template>
</InputTool>
<InputTool tips="代码">
<template #iconSlot>
<CreditCard />
</template>
</InputTool>
<InputTool tips="位置">
<template #iconSlot>
<LocationInformation />
</template>
</InputTool>
</div>
<div class="right-tools">
<InputTool tips="历史记录">
<template #iconSlot>
<Clock />
</template>
</InputTool>
</div>
</div>
</el-header>
<el-main class="input-box-main">
<InputEditor
:draft="choosedSession?.draft"
@exportContent="handleExportContent"
></InputEditor>
2024-09-05 10:49:44 +08:00
</el-main>
</el-container>
</div>
</el-main>
</el-container>
</el-main>
</el-container>
</template>
<style lang="scss" scoped>
.msg-container-hole {
height: 100%;
user-select: none;
.msg-aside {
height: 100%;
position: relative;
.msg-aside-main {
2024-09-05 17:41:27 +08:00
width: 100%;
height: 100%;
display: flex; // 需要flex布局否则session-list的滚动条会有问题
flex-direction: column;
2024-09-05 17:41:27 +08:00
overflow: hidden; // 禁用它的滚动条
.header {
2024-09-05 10:49:44 +08:00
margin-top: 10px;
margin-bottom: 10px;
2024-09-05 17:41:27 +08:00
display: flex;
}
.session-list {
width: 100%;
overflow-y: scroll; // 用它的滚动条
2024-09-05 10:49:44 +08:00
}
}
}
.msg-box {
padding: 0;
2024-09-05 20:57:33 +08:00
overflow: hidden; // 禁用它的滚动条
2024-09-05 10:49:44 +08:00
.backgroup-image {
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
.header {
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
2024-09-05 23:02:44 +08:00
.show-name-id {
display: flex;
align-items: center;
user-select: text;
.show-name {
font-size: 16px;
font-weight: bold;
}
.show-id {
margin-left: 10px;
font-size: 14px;
color: gray;
}
2024-09-05 10:49:44 +08:00
}
.action-set {
margin-right: 20px;
.action-button {
border: 0;
}
}
}
.body {
width: 100%;
height: 100%;
padding: 0;
display: flex;
flex-direction: column;
2024-09-09 16:45:30 +08:00
overflow: hidden; // 禁用它的滚动条
position: relative;
2024-09-05 10:49:44 +08:00
.top-loading {
2024-09-05 10:49:44 +08:00
width: 100%;
height: 30px;
position: absolute;
top: 0;
2024-09-09 12:00:46 +08:00
display: flex;
justify-content: center;
align-items: center;
2024-09-08 23:49:43 +08:00
.loading-box {
color: #409eff;
font-size: 14px;
2024-09-08 23:49:43 +08:00
}
:deep(.circular) {
width: 24px;
height: 24px;
position: absolute;
top: 12px;
left: -12px;
}
}
.show-box {
width: 100%;
display: flex;
flex: 1;
overflow-y: scroll; // 用它的滚动条
2024-09-08 23:49:43 +08:00
.message-main {
width: 100%;
height: 100%;
padding: 20px;
padding-right: 15px;
2024-09-09 16:45:30 +08:00
.no-more-message {
width: 100%;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: gray;
user-select: text;
}
2024-09-08 23:49:43 +08:00
}
2024-09-05 10:49:44 +08:00
}
.input-box {
width: 100%;
display: flex;
position: relative;
.input-box-header {
width: 100%;
2024-09-05 11:24:58 +08:00
height: auto;
2024-09-05 10:49:44 +08:00
padding: 0;
2024-09-05 11:24:58 +08:00
display: flex;
flex-direction: column;
2024-09-05 10:49:44 +08:00
.tool-set {
display: flex;
justify-content: space-between;
.left-tools {
display: flex;
}
.right-tools {
margin-right: 10px;
}
}
}
.input-box-main {
width: 100%;
padding: 0;
}
}
}
}
}
}
</style>