|
@@ -1,82 +1,95 @@
|
|
|
<template>
|
|
|
- <div class="chat-container">
|
|
|
- <!-- 左侧用户列表 -->
|
|
|
- <div class="user-list">
|
|
|
- <div class="user-list-header">
|
|
|
- <el-input v-model="searchQuery" placeholder="搜索" prefix-icon="el-icon-search" clearable />
|
|
|
- </div>
|
|
|
- <div class="user-list-content">
|
|
|
- <div v-for="user in filteredUsers" :key="user.userId" class="user-item"
|
|
|
- :class="{ active: currentUser?.userId === user.userId }" @click="selectUser(user)">
|
|
|
- <el-avatar :size="40" :src="user.conversationAvatar" />
|
|
|
- <div class="user-info">
|
|
|
- <div class="user-name">{{ user.userName }}</div>
|
|
|
- <div class="last-message">{{ user.newestMsgContent }}</div>
|
|
|
- </div>
|
|
|
- <div class="message-time">{{ formatTime(user.newestMsgTime) }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="chat-container">
|
|
|
+ <!-- 左侧用户列表 -->
|
|
|
+ <div class="user-list">
|
|
|
+ <div class="user-list-header">
|
|
|
+ <el-input v-model="searchQuery" placeholder="搜索" prefix-icon="el-icon-search" clearable />
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <div class="user-list-content">
|
|
|
+ <div v-for="user in filteredUsers" :key="user.userId" class="user-item"
|
|
|
+ :class="{ active: currentUser?.userId === user.userId }" @click="selectUser(user)">
|
|
|
+ <el-avatar :size="40" :src="user.conversationAvatar || logo" />
|
|
|
+ <div class="user-info">
|
|
|
+ <div class="user-name">{{ user.conversationTitle }}</div>
|
|
|
+ <div class="last-message">{{ user.newestMsgContent }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="message-time2">
|
|
|
+ <div class="newestMsgTime">{{ formatTime(user.newestMsgTime) }}</div>
|
|
|
+ <div class="msgUnreadCount" v-if="user.msgUnreadCount && user.msgUnreadCount>0">{{ user.msgUnreadCount }}</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 右侧聊天区域 -->
|
|
|
- <div class="chat-area">
|
|
|
- <template v-if="currentUser">
|
|
|
- <div class="chat-header">
|
|
|
- <span>{{ currentUser.nickname }}</span>
|
|
|
- </div>
|
|
|
- <div class="message-list" ref="messageList">
|
|
|
- <div v-for="message in currentMessages.vos" :key="message.senderId" class="message-item"
|
|
|
- :class="{ 'message-self': message.senderId === userId }">
|
|
|
- <el-avatar :size="40"
|
|
|
- :src="message.senderId === userId ? userStore.avatar : currentMessages.conversationAvatar" />
|
|
|
- <div class="message-content">
|
|
|
- <div class="message-time"
|
|
|
- :style="{ textAlign: message.senderId === userId ? 'right' : 'left' }">{{
|
|
|
- message.msgSendTime }}</div>
|
|
|
- <div class="message-bubble">
|
|
|
- <template v-if="message.msgType === '1'">
|
|
|
- {{ message.msgContent }}
|
|
|
- </template>
|
|
|
- <template v-else-if="message.type === '2'">
|
|
|
- <el-image :src="message.msgContent" :preview-src-list="[message.msgContent]"
|
|
|
- fit="cover" class="message-image" />
|
|
|
- </template>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="message-input">
|
|
|
- <!-- <div class="input-toolbar">
|
|
|
+ <!-- 右侧聊天区域 -->
|
|
|
+ <div class="chat-area">
|
|
|
+ <template v-if="currentUser">
|
|
|
+ <div class="chat-header">
|
|
|
+ <span>{{ currentUser.nickname }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="message-list" ref="messageList">
|
|
|
+ <div v-if="isLoading" class="loading-messages">
|
|
|
+ <el-icon class="is-loading">
|
|
|
+ <Loading />
|
|
|
+ </el-icon>
|
|
|
+ <span>加载更多消息...</span>
|
|
|
+ </div>
|
|
|
+ <div v-for="message in messages" :key="message.senderId" class="message-item"
|
|
|
+ :class="{ 'message-self': message.senderId === districtCode }">
|
|
|
+ <el-avatar :size="40"
|
|
|
+ :src="message.senderId === districtCode ? userStore.avatar : currentMessages.conversationAvatar || logo" />
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-time" :style="{ textAlign: message.senderId === districtCode ? 'right' : 'left' }">{{
|
|
|
+ message.msgSendTime }}</div>
|
|
|
+ <div class="message-bubble">
|
|
|
+ <template v-if="message.msgType === '1'">
|
|
|
+ {{ message.msgContent }}
|
|
|
+ </template>
|
|
|
+ <template v-else-if="message.type === '2'">
|
|
|
+ <el-image :src="message.msgContent" :preview-src-list="[message.msgContent]" fit="cover"
|
|
|
+ class="message-image" />
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="message-input">
|
|
|
+ <!-- <div class="input-toolbar">
|
|
|
<el-upload action="#" :auto-upload="false" :show-file-list="false"
|
|
|
:on-change="handleImageUpload">
|
|
|
<el-button type="text" :icon="Picture" title="发送图片">图片</el-button>
|
|
|
</el-upload>
|
|
|
</div> -->
|
|
|
- <div class="input-area">
|
|
|
- <el-input v-model="messageInput" type="textarea" :rows="3" placeholder="请输入消息"
|
|
|
- @keyup.enter.native="sendMessage" />
|
|
|
- <el-button type="primary" @click="sendMessage('text')">发送</el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div v-else class="no-chat-selected">
|
|
|
- 请选择一个聊天对象
|
|
|
- </div>
|
|
|
+ <div class="input-area">
|
|
|
+ <el-input v-model="messageInput" type="textarea" :rows="3" placeholder="请输入消息"
|
|
|
+ @keyup.enter.native="sendMessage" />
|
|
|
+ <el-button type="primary" @click="sendMessage('text')">发送</el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </template>
|
|
|
+ <div v-else class="no-chat-selected">
|
|
|
+ 请选择一个聊天对象
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed, onMounted, nextTick } from 'vue'
|
|
|
+import { ref, computed, onMounted, nextTick, onUnmounted } from 'vue'
|
|
|
import dayjs from 'dayjs'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
-import { getList, sendMsg, getListConversationInfo, setRead } from '@/api/conversation'
|
|
|
+import { getList, sendMsg, getListConversationInfo, setRead, getHistoryMsg } from '@/api/conversation'
|
|
|
+// import WebSocketClient from './WebSocketClient.js';
|
|
|
+import { debounce } from '@/utils/index.js';
|
|
|
import useUserStore from '@/store/modules/user'
|
|
|
-import WebSocketClient from './WebSocketClient.js';
|
|
|
const userStore = useUserStore();
|
|
|
-
|
|
|
+import logo from '@/assets/logo/logo.png'
|
|
|
const userId = computed(() => {
|
|
|
- return userStore.user.deptId
|
|
|
+ return userStore.user.userId
|
|
|
+})
|
|
|
+const districtCode = computed(() => {
|
|
|
+ return userStore.user.districtCode
|
|
|
})
|
|
|
console.log("TCL: userId -> userId", userId)
|
|
|
|
|
@@ -88,92 +101,157 @@ const searchQuery = ref('')
|
|
|
const currentUser = ref(null)
|
|
|
const messageInput = ref('')
|
|
|
const messageList = ref(null)
|
|
|
+const isLoading = ref(false)
|
|
|
+const currentPage = ref(1)
|
|
|
+const hasMore = ref(true)
|
|
|
|
|
|
// 用户列表
|
|
|
const filteredUsers = ref([]);
|
|
|
|
|
|
// 当前用户的消息
|
|
|
const currentMessages = ref({
|
|
|
- vos: []
|
|
|
+ vos: []
|
|
|
})
|
|
|
|
|
|
const isToday = (date) => {
|
|
|
- return dayjs(date).isSame(dayjs(), 'day');
|
|
|
+ return dayjs(date).isSame(dayjs(), 'day');
|
|
|
};
|
|
|
|
|
|
const isYesterday = (date) => {
|
|
|
- return dayjs(date).isSame(dayjs().subtract(1, 'day'), 'day');
|
|
|
+ return dayjs(date).isSame(dayjs().subtract(1, 'day'), 'day');
|
|
|
};
|
|
|
const handlerData = (dates) => {
|
|
|
- const date = dayjs(dates);
|
|
|
- if (isToday(dates)) {
|
|
|
- return date.format('HH:MM');;
|
|
|
- } else if (isYesterday(dates)) {
|
|
|
- return '昨天';
|
|
|
- } else {
|
|
|
- return date.format('YY/MM/DD'); // 或者其他格式如 'YYYY年MM月DD日'
|
|
|
- }
|
|
|
+ const date = dayjs(dates);
|
|
|
+ if (isToday(dates)) {
|
|
|
+ return date.format('HH:MM');;
|
|
|
+ } else if (isYesterday(dates)) {
|
|
|
+ return '昨天';
|
|
|
+ } else {
|
|
|
+ return date.format('YY/MM/DD'); // 或者其他格式如 'YYYY年MM月DD日'
|
|
|
+ }
|
|
|
}
|
|
|
// 格式化时间
|
|
|
const formatTime = (time) => {
|
|
|
- return handlerData(time)
|
|
|
+ return handlerData(time)
|
|
|
}
|
|
|
|
|
|
// 选择用户
|
|
|
-const selectUser = async(user) => {
|
|
|
- try {
|
|
|
- console.log("TCL: selectUser -> user", user)
|
|
|
- const res = await getListConversationInfo({
|
|
|
- conversationRecordId: user.conversationRecordId,
|
|
|
- })
|
|
|
- const data = res.data;
|
|
|
- data.vos = data.vos.reverse(); //倒叙处理
|
|
|
- currentMessages.value = data;
|
|
|
- currentUser.value = user
|
|
|
+const selectUser = async (user) => {
|
|
|
+ try {
|
|
|
+ console.log("TCL: selectUser -> user", user)
|
|
|
+ currentPage.value = 1
|
|
|
+ hasMore.value = true
|
|
|
+ await setRead({...user, conversationRecordId: user.conversationRecordId, system: 3, }); //设置已读
|
|
|
+ const res = await getListConversationInfo({
|
|
|
+ conversationRecordId: user.conversationRecordId,
|
|
|
+ pageNum: currentPage.value,
|
|
|
+ pageSize: 20
|
|
|
+ })
|
|
|
+ const data = res.data;
|
|
|
+ data.vos = data.vos.reverse(); //倒叙处理
|
|
|
+ currentMessages.value = data;
|
|
|
+ messages.value = data.vos;
|
|
|
+ currentUser.value = user
|
|
|
nextTick(() => {
|
|
|
- scrollToBottom()
|
|
|
+ scrollToBottom()
|
|
|
})
|
|
|
- } catch (error) {
|
|
|
-
|
|
|
+ } catch (error) {
|
|
|
+ console.log("TCL: selectUser -> error", error)
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载更多消息
|
|
|
+const loadMoreMessages = debounce(async () => {
|
|
|
+ if (isLoading.value || !hasMore.value || !currentUser.value) return
|
|
|
+
|
|
|
+ // isLoading.value = true
|
|
|
+ // console.log('xx');
|
|
|
+ // setTimeout(() => {
|
|
|
+ // isLoading.value = false
|
|
|
+ // }, 1000)
|
|
|
+
|
|
|
+ // return
|
|
|
+
|
|
|
+ try {
|
|
|
+ isLoading.value = true
|
|
|
+ const oldScrollHeight = messageList.value.scrollHeight
|
|
|
+ const conversationMsgRecordId = messages.value[messages.value.length - 1].conversationMsgRecordId;
|
|
|
+ const res = await getHistoryMsg({
|
|
|
+ conversationMsgRecordId,
|
|
|
+ conversationRecordId: currentUser.value.conversationRecordId
|
|
|
+ })
|
|
|
+
|
|
|
+ const data = res.data
|
|
|
+ console.log("TCL: loadMoreMessages -> data", data)
|
|
|
+ if (data && data.length > 0) {
|
|
|
+ const data_reverse = data.reverse()
|
|
|
+ console.log("TCL: loadMoreMessages -> data_reverse", data_reverse)
|
|
|
+ const mes = [...data, ...messages.value]
|
|
|
+ messages.value = mes;
|
|
|
+ console.log("TCL: loadMoreMessages -> messages.value", messages.value)
|
|
|
+
|
|
|
+ } else {
|
|
|
+ hasMore.value = false
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ const newScrollHeight = messageList.value.scrollHeight
|
|
|
+ messageList.value.scrollTop = newScrollHeight - oldScrollHeight
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载更多消息失败:', error)
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
+ }
|
|
|
+}, 500)
|
|
|
+
|
|
|
+// 处理滚动事件
|
|
|
+const handleScroll = () => {
|
|
|
+ if (!messageList.value) return
|
|
|
+
|
|
|
+ const { scrollTop } = messageList.value
|
|
|
+
|
|
|
+ if (scrollTop < 50 && !isLoading.value) {
|
|
|
+
|
|
|
+ loadMoreMessages()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 发送消息
|
|
|
const sendMessage = async (contentType) => {
|
|
|
- try {
|
|
|
- if (!messageInput.value.trim()) {
|
|
|
- ElMessage.warning('消息不能为空')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (!currentUser.value) {
|
|
|
- ElMessage.warning('请先选择一个聊天对象')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const newMessage = {
|
|
|
- conversationRecordId: currentUser.value.conversationRecordId,
|
|
|
- msgContent: messageInput.value,
|
|
|
- msgSendTime: dayjs(new Date().getTime()).format('YYYY-MM-DD HH:MM:SS') ,
|
|
|
- msgType: contentType == 'text' ? '1' : '2',//消息类型 1文字消息 2图片消息
|
|
|
- userId: userId.value,
|
|
|
- senderId: userId.value,
|
|
|
- system: 3,
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- await sendMsg(newMessage);
|
|
|
- currentMessages.value.vos.push(newMessage);
|
|
|
- messageInput.value = ''
|
|
|
- nextTick(() => {
|
|
|
- scrollToBottom()
|
|
|
- })
|
|
|
- } catch (error) {
|
|
|
- console.log("TCL: sendMessage -> error", error)
|
|
|
+ try {
|
|
|
+ if (!messageInput.value.trim()) {
|
|
|
+ ElMessage.warning('消息不能为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!currentUser.value) {
|
|
|
+ ElMessage.warning('请先选择一个聊天对象')
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
+ const newMessage = {
|
|
|
+ ...currentUser.value,
|
|
|
+ conversationRecordId: currentUser.value.conversationRecordId,
|
|
|
+ msgContent: messageInput.value,
|
|
|
+ msgSendTime: dayjs(new Date().getTime()).format('YYYY-MM-DD HH:MM:SS'),
|
|
|
+ msgType: contentType == 'text' ? '1' : '2',//消息类型 1文字消息 2图片消息
|
|
|
+ system: 3,
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ await sendMsg(newMessage);
|
|
|
+ currentMessages.value.vos.push(newMessage);
|
|
|
+ messageInput.value = ''
|
|
|
+ nextTick(() => {
|
|
|
+ scrollToBottom()
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.log("TCL: sendMessage -> error", error)
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
|
|
@@ -206,238 +284,298 @@ const sendMessage = async (contentType) => {
|
|
|
|
|
|
// 滚动到底部
|
|
|
const scrollToBottom = () => {
|
|
|
- if (messageList.value) {
|
|
|
- messageList.value.scrollTop = messageList.value.scrollHeight
|
|
|
- }
|
|
|
+ if (messageList.value) {
|
|
|
+ messageList.value.scrollTop = messageList.value.scrollHeight
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-const getMList = async () => {
|
|
|
- try {
|
|
|
- const res = await getList({
|
|
|
- system: 3
|
|
|
- })
|
|
|
- filteredUsers.value = res.rows;
|
|
|
- res.rows[0] && selectUser(res.rows[0])
|
|
|
- } catch (error) {
|
|
|
+const getMList = async (type) => {
|
|
|
+ try {
|
|
|
+ const res = await getList({
|
|
|
+ system: 3
|
|
|
+ })
|
|
|
+ filteredUsers.value = res.rows;
|
|
|
+ type && res.rows[0] && selectUser(res.rows[0])
|
|
|
+ } catch (error) {
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
let ws = null;
|
|
|
|
|
|
|
|
|
const soketInit = () => {
|
|
|
- try {
|
|
|
- //获取账户时,连接soket
|
|
|
- ws = new WebSocketClient(userId.value);
|
|
|
- // 连接WebSocket
|
|
|
- ws.connect()
|
|
|
-
|
|
|
- // 监听连接事件
|
|
|
- ws.on('connect', () => {
|
|
|
- console.log('连接成功')
|
|
|
- })
|
|
|
-
|
|
|
- // 监听断开连接事件
|
|
|
- ws.on('disconnect', () => {
|
|
|
- console.log('连接断开')
|
|
|
- })
|
|
|
- // 监听错误事件
|
|
|
- ws.on('error', (error) => {
|
|
|
- console.error('发生错误:', error)
|
|
|
- })
|
|
|
- // 监听消息事件
|
|
|
- ws.on('message', (res) => {
|
|
|
- console.log('收到消息:', res)
|
|
|
- const data = JSON.parse(res.data);
|
|
|
- console.log("TCL: soketInit -> data", data)
|
|
|
- if (res.type === 'msgNew') {
|
|
|
- currentMessages.value.push({...res.data})
|
|
|
- }
|
|
|
+ ws = userStore.ws;
|
|
|
+ console.log("TCL: soketInit -> ws", ws)
|
|
|
+ try {
|
|
|
+ // //获取账户时,连接soket
|
|
|
+ // ws = new WebSocketClient(userId.value);
|
|
|
+ // // 连接WebSocket
|
|
|
+ // ws.connect()
|
|
|
+
|
|
|
+ // // 监听连接事件
|
|
|
+ // ws.on('connect', () => {
|
|
|
+ // console.log('连接成功')
|
|
|
+ // })
|
|
|
+
|
|
|
+ // // 监听断开连接事件
|
|
|
+ // ws.on('disconnect', () => {
|
|
|
+ // console.log('连接断开')
|
|
|
+ // })
|
|
|
+ // // 监听错误事件
|
|
|
+ // ws.on('error', (error) => {
|
|
|
+ // console.error('发生错误:', error)
|
|
|
+ // })
|
|
|
+ // 监听消息事件
|
|
|
+ ws.on('message', (res) => {
|
|
|
+ console.log('收到消息:', res,currentUser.value.conversationRecordId , res.data.conversationRecordId)
|
|
|
+ // const data = JSON.parse(res.data);
|
|
|
+
|
|
|
+ if (res.type === 'msgNew' && currentUser.value.conversationRecordId === res.data.conversationRecordId) {
|
|
|
+ console.log("TCL: soketInit -> data", res.data)
|
|
|
+ messages.value.push(res.data)
|
|
|
+ nextTick(() => {
|
|
|
+ scrollToBottom()
|
|
|
})
|
|
|
+ }
|
|
|
+ if(res.type ==='msgUnreadCount' || currentUser.value.conversationRecordId !== res.data.conversationRecordId){
|
|
|
+ getMList(false);//获取用户列表
|
|
|
+ }
|
|
|
+ })
|
|
|
|
|
|
- } catch (error) {
|
|
|
- console.log("TCL: soketInit -> error", error)
|
|
|
+ } catch (error) {
|
|
|
+ console.log("TCL: soketInit -> error", error)
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
+
|
|
|
+ getMList(true);//获取用户列表
|
|
|
|
|
|
+ setTimeout(() => {
|
|
|
+ // 添加滚动监听
|
|
|
+ if (messageList.value) {
|
|
|
+ messageList.value.addEventListener('scroll', handleScroll)
|
|
|
+ }
|
|
|
soketInit();
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- getMList();//获取用户列表
|
|
|
+ }, 500)
|
|
|
})
|
|
|
-</script>
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ // 移除滚动监听
|
|
|
+ if (messageList.value) {
|
|
|
+ messageList.value.removeEventListener('scroll', handleScroll)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ </script>
|
|
|
|
|
|
<style scoped>
|
|
|
.chat-container {
|
|
|
- display: flex;
|
|
|
- height: 100vh;
|
|
|
- background-color: #f5f5f5;
|
|
|
+ display: flex;
|
|
|
+ height: calc(100vh - 130px);
|
|
|
+ background-color: #f5f5f5;
|
|
|
}
|
|
|
|
|
|
.user-list {
|
|
|
- width: 300px;
|
|
|
- background-color: #fff;
|
|
|
- border-right: 1px solid #e6e6e6;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ width: 300px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-right: 1px solid #e6e6e6;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.user-list-header {
|
|
|
- padding: 10px;
|
|
|
- border-bottom: 1px solid #e6e6e6;
|
|
|
+ padding: 10px;
|
|
|
+ border-bottom: 1px solid #e6e6e6;
|
|
|
}
|
|
|
|
|
|
.user-list-content {
|
|
|
- flex: 1;
|
|
|
- overflow-y: auto;
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.user-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 10px;
|
|
|
- cursor: pointer;
|
|
|
- transition: background-color 0.3s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background-color 0.3s;
|
|
|
}
|
|
|
|
|
|
.user-item:hover {
|
|
|
- background-color: #f5f5f5;
|
|
|
+ background-color: #f5f5f5;
|
|
|
}
|
|
|
|
|
|
.user-item.active {
|
|
|
- background-color: #e6f7ff;
|
|
|
+ background-color: #e6f7ff;
|
|
|
}
|
|
|
|
|
|
.user-info {
|
|
|
- flex: 1;
|
|
|
- margin-left: 10px;
|
|
|
- overflow: hidden;
|
|
|
+ flex: 1;
|
|
|
+ margin-left: 10px;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.user-name {
|
|
|
- font-weight: bold;
|
|
|
- margin-bottom: 4px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
}
|
|
|
|
|
|
.last-message {
|
|
|
- color: #999;
|
|
|
- font-size: 12px;
|
|
|
- white-space: nowrap;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
+ color: #999;
|
|
|
+ font-size: 12px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
}
|
|
|
|
|
|
.message-time {
|
|
|
- font-size: 12px;
|
|
|
- color: #999;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chat-area {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- background-color: #fff;
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background-color: #fff;
|
|
|
}
|
|
|
|
|
|
.chat-header {
|
|
|
- padding: 10px;
|
|
|
- border-bottom: 1px solid #e6e6e6;
|
|
|
- font-weight: bold;
|
|
|
+ padding: 10px;
|
|
|
+ border-bottom: 1px solid #e6e6e6;
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.message-list {
|
|
|
- flex: 1;
|
|
|
- overflow-y: auto;
|
|
|
- padding: 20px;
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 20px;
|
|
|
}
|
|
|
|
|
|
.message-item {
|
|
|
- display: flex;
|
|
|
- margin-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.message-item.message-self {
|
|
|
- flex-direction: row-reverse;
|
|
|
+ flex-direction: row-reverse;
|
|
|
}
|
|
|
|
|
|
.message-content {
|
|
|
- margin: 0 10px;
|
|
|
- max-width: 60%;
|
|
|
+ margin: 0 10px;
|
|
|
+ max-width: 60%;
|
|
|
}
|
|
|
|
|
|
.message-time {
|
|
|
- font-size: 12px;
|
|
|
- color: #999;
|
|
|
- margin-bottom: 4px;
|
|
|
- text-align: left;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ text-align: left;
|
|
|
+
|
|
|
+}
|
|
|
+.message-time2 {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ text-align: left;
|
|
|
+
|
|
|
+ display: flex;
|
|
|
+ align-items: end;
|
|
|
+ justify-content: center;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.msgUnreadCount {
|
|
|
+ background: red;
|
|
|
+ color: #fff;
|
|
|
+ margin-top: 4px;
|
|
|
+ width: 15px;
|
|
|
+ height: 15px;
|
|
|
+
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
-
|
|
|
.message-bubble {
|
|
|
- background-color: #f5f5f5;
|
|
|
- padding: 10px;
|
|
|
- border-radius: 4px;
|
|
|
- word-break: break-all;
|
|
|
-
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ padding: 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ word-break: break-all;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
.message-self .message-bubble {
|
|
|
- background-color: #95ec69;
|
|
|
- text-align: right;
|
|
|
- width: 100%;
|
|
|
+ background-color: #95ec69;
|
|
|
+ text-align: right;
|
|
|
+ width: 100%;
|
|
|
}
|
|
|
|
|
|
.message-image {
|
|
|
- max-width: 200px;
|
|
|
- max-height: 200px;
|
|
|
- border-radius: 4px;
|
|
|
+ max-width: 200px;
|
|
|
+ max-height: 200px;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.message-input {
|
|
|
- border-top: 1px solid #e6e6e6;
|
|
|
- padding: 10px;
|
|
|
- display: flex;
|
|
|
+ border-top: 1px solid #e6e6e6;
|
|
|
+ padding: 10px;
|
|
|
+ display: flex;
|
|
|
}
|
|
|
|
|
|
.input-toolbar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- padding: 8px 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 0;
|
|
|
}
|
|
|
|
|
|
.input-toolbar .el-button {
|
|
|
- padding: 4px 8px;
|
|
|
- font-size: 14px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.input-toolbar .el-button:hover {
|
|
|
- background-color: #f5f5f5;
|
|
|
- border-radius: 4px;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.input-area {
|
|
|
- display: flex;
|
|
|
- gap: 10px;
|
|
|
- padding-top: 10px;
|
|
|
- flex: 1;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ padding-top: 10px;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
|
|
|
.input-area .el-textarea {
|
|
|
- flex: 1;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
|
|
|
.no-chat-selected {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- height: 100%;
|
|
|
- color: #999;
|
|
|
- font-size: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ color: #999;
|
|
|
+ font-size: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-messages {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 10px;
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
+
|
|
|
+.loading-messages .el-icon{
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
</style>
|