319 lines
8.6 KiB
Vue
319 lines
8.6 KiB
Vue
<template>
|
|
<div id="MsgList">
|
|
<aside class="list-panel">
|
|
<div class="search-section">
|
|
<div class="search-box">
|
|
<span class="search-icon"><i v-html="feather.icons['search'].toSvg({width:15,height:15})"></i></span>
|
|
<input v-model="searchQuery" placeholder="搜索" />
|
|
</div>
|
|
<div class="addMenu">
|
|
<AddMenu :menu-list="addMenuList" @action-active="actionHandler"/>
|
|
</div>
|
|
</div>
|
|
<div v-if="msgTitleShow" class="showMsg" @click="requestNotificationPermission">
|
|
<i style="color: red;line-height:0;" v-html="feather.icons['alert-circle'].toSvg({width:14})"></i>
|
|
<span>新消息无法通知,点我授予通知权限</span>
|
|
</div>
|
|
|
|
<div class="scroll-area">
|
|
<div v-for="s in filteredSessions" :key="s.id"
|
|
class="list-item" :class="{active: activeId === s.id}" @click="selectSession(s)">
|
|
<div class="avatar-container">
|
|
<img :src="s.targetAvatar ?? defaultAvatar" class="avatar-std" />
|
|
<span v-if="s.unreadCount > 0" class="unread-badge">{{ s.unreadCount ?? 0 }}</span>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name-row">
|
|
<span class="name">{{ s.targetName ?? '未知用户' }}</span>
|
|
<span class="time">{{ formatDate(s.dateTime) ?? '1970/1/1 00:00:00' }}</span>
|
|
</div>
|
|
<div class="last-msg">{{ s.lastMessage ?? '获取消息内容失败' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<RouterView></RouterView>
|
|
<SearchUser v-model="searchUserModal"/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { useRouter } from 'vue-router'
|
|
import { ref, computed, nextTick, onMounted } from 'vue'
|
|
import { messageService } from '@/services/message'
|
|
import defaultAvatar from '@/assets/default_avatar.png'
|
|
import { formatDate } from '@/utils/formatDate'
|
|
import { useConversationStore } from '@/stores/conversation'
|
|
import AddMenu from '@/components/addMenu.vue'
|
|
import feather from 'feather-icons'
|
|
import SearchUser from '@/components/user/SearchUser.vue'
|
|
import { useBrowserNotification } from '@/services/useBrowserNotification'
|
|
|
|
const conversationStore = useConversationStore();
|
|
const router = useRouter();
|
|
const browserNotification = useBrowserNotification();
|
|
|
|
const searchQuery = ref('')
|
|
const activeId = ref(1)
|
|
const conversations = ref([]);
|
|
const searchUserModal = ref(false);
|
|
const msgTitleShow = ref(false);
|
|
const addMenuList = [
|
|
{
|
|
text: '发起群聊',
|
|
action: 'createGroup',
|
|
// 气泡/对话图标
|
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>`
|
|
},
|
|
{
|
|
text: '添加朋友',
|
|
action: 'addFriend',
|
|
// 人像+号图标
|
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="17" y1="11" x2="23" y2="11"></line></svg>`
|
|
},
|
|
{
|
|
text: '新建笔记',
|
|
action: 'newNote',
|
|
// 书本/笔记本图标
|
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>`
|
|
}
|
|
];
|
|
|
|
const filteredSessions = computed(() => conversationStore.sortedConversations.filter(s => s.targetName.includes(searchQuery.value)))
|
|
|
|
function selectSession(s) {
|
|
activeId.value = s.id
|
|
router.push(`/messages/chat/${s.id}`)
|
|
}
|
|
|
|
function actionHandler(type){
|
|
switch(type){
|
|
case 'addFriend':
|
|
searchUserModal.value = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
async function requestNotificationPermission(){
|
|
await browserNotification.requestPermission();
|
|
if(Notification.permission === "granted") msgTitleShow.value = false;
|
|
}
|
|
|
|
async function loadConversation() {
|
|
const res = await messageService.getConversations();
|
|
conversations.value = res.data;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await conversationStore.loadUserConversations();
|
|
if(Notification.permission != "granted") msgTitleShow.value = true;
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
<style scoped>
|
|
|
|
#MsgList {
|
|
display: flex;
|
|
flex: 1;
|
|
}
|
|
|
|
/* 2. 列表区修复 */
|
|
.list-panel {
|
|
width: 250px;
|
|
flex-shrink: 0;
|
|
background: #eee;
|
|
border-right: 1px solid #d6d6d6;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.showMsg {
|
|
/* width: 10px; */
|
|
height: 20px;
|
|
background: #e3f98d;
|
|
font-size: 12px;
|
|
display: flex;
|
|
/* text-align: center; */
|
|
flex-wrap: nowrap;
|
|
align-content: center;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: red;
|
|
cursor: pointer;
|
|
}
|
|
|
|
|
|
/* 修复:搜索框美化 */
|
|
.search-section {
|
|
padding: 20px 12px 10px 12px;
|
|
display: flex;
|
|
}
|
|
.search-box {
|
|
flex: 9;
|
|
display: flex;
|
|
align-items: center;
|
|
background: #dbdbdb;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
gap: 5px;
|
|
}
|
|
.search-icon { font-size: 12px; color: #666; }
|
|
.search-box input {
|
|
background: transparent;
|
|
border: none;
|
|
outline: none;
|
|
font-size: 12px;
|
|
width: 100%;
|
|
}
|
|
.addMenu {
|
|
flex: 1;
|
|
padding-left: 5px;
|
|
}
|
|
|
|
.scroll-area { flex: 1; overflow-y: auto; }
|
|
|
|
/* 3. 聊天主面板修复 */
|
|
.chat-panel {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #f5f5f5;
|
|
min-width: 0;
|
|
}
|
|
|
|
.chat-header {
|
|
height: 60px;
|
|
padding: 0 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.chat-history {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 20px 30px;
|
|
}
|
|
|
|
/* 修复:本人消息右侧对齐逻辑 */
|
|
.msg {
|
|
display: flex;
|
|
margin-bottom: 24px;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* 别人发的:默认靠左 */
|
|
.msg.other { flex-direction: row; }
|
|
|
|
/* 本人发的:翻转排列方向,靠右显示 */
|
|
.msg.mine {
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
.msg-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
max-width: 70%;
|
|
}
|
|
|
|
/* 修复:本人消息文字和时间戳也需要右对齐 */
|
|
.msg.mine .msg-content {
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.bubble {
|
|
padding: 9px 14px;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
word-break: break-all;
|
|
position: relative;
|
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
|
}
|
|
|
|
.other .bubble { background: #fff; color: #333; }
|
|
.mine .bubble { background: #95ec69; color: #000; }
|
|
|
|
.msg-time {
|
|
font-size: 11px;
|
|
color: #b2b2b2;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* 头像样式统一 */
|
|
.avatar-std { width: 40px; height: 40px; border-radius: 4px; object-fit: cover; }
|
|
.avatar-chat { width: 38px; height: 38px; border-radius: 4px; object-fit: cover; flex-shrink: 0; }
|
|
|
|
/* 未读气泡 */
|
|
.avatar-container { position: relative; }
|
|
.unread-badge {
|
|
position: absolute;
|
|
top: -5px;
|
|
right: -5px;
|
|
background: #ff4d4f;
|
|
color: #fff;
|
|
font-size: 10px;
|
|
padding: 0 4px;
|
|
min-width: 16px;
|
|
height: 16px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid #fff;
|
|
}
|
|
|
|
/* 输入框区域修复 */
|
|
.chat-footer {
|
|
height: 160px;
|
|
background: #fff;
|
|
border-top: 1px solid #e0e0e0;
|
|
padding: 10px 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.toolbar { display: flex; gap: 12px; margin-bottom: 5px; font-size: 20px; color: #666; }
|
|
.toolbar button { background: none; border: none; cursor: pointer; opacity: 0.7; }
|
|
|
|
textarea {
|
|
flex: 1;
|
|
border: none;
|
|
outline: none;
|
|
resize: none;
|
|
font-family: inherit;
|
|
font-size: 14px;
|
|
padding: 5px 0;
|
|
}
|
|
|
|
.send-row { display: flex; justify-content: flex-end; }
|
|
.send-btn {
|
|
background: #f5f5f5;
|
|
color: #07c160;
|
|
border: 1px solid #e0e0e0;
|
|
padding: 5px 20px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
}
|
|
.send-btn:hover { background: #e2e2e2; }
|
|
|
|
/* 列表美化 */
|
|
.list-item { display: flex; padding: 12px; gap: 12px; cursor: pointer; }
|
|
.list-item.active { background: #c6c6c6; }
|
|
.list-item:hover:not(.active) { background: #ddd; }
|
|
.info { flex: 1; overflow: hidden; }
|
|
.name-row { display: flex; justify-content: space-between; align-items: center; }
|
|
.name { font-size: 14px; font-weight: 500; }
|
|
.time { font-size: 11px; color: #999; }
|
|
.last-msg { font-size: 12px; color: #888; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
.nav-item { font-size: 24px; cursor: pointer; opacity: 0.5; }
|
|
.nav-item.active { opacity: 1; }
|
|
</style> |