IM/frontend/web/src/views/contact/ContactList.vue

327 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="contact-container">
<aside class="contact-list-panel">
<div class="search-section">
<div class="search-box">
<span class="search-icon">🔍</span>
<input v-model="searchQuery" placeholder="搜索联系人" />
</div>
</div>
<div class="scroll-area">
<div class="fixed-entries">
<div class="list-item mini">
<div class="icon-box orange">👤+</div>
<div class="name">新的朋友</div>
</div>
<div class="list-item mini">
<div class="icon-box green">👥</div>
<div class="name">群聊</div>
</div>
<div class="list-item mini">
<div class="icon-box blue">🏷️</div>
<div class="name">标签</div>
</div>
</div>
<div class="group-title">我的好友</div>
<div v-for="c in filteredContacts"
:key="c.id"
class="list-item"
:class="{active: activeContactId === c.id}"
@click="activeContactId = c.id">
<img :src="c.avatar" class="avatar-std" />
<div class="info">
<div class="name">{{ c.name }}</div>
</div>
</div>
</div>
</aside>
<main class="profile-main">
<div v-if="currentContact" class="profile-card">
<header class="profile-header">
<div class="text-info">
<h2 class="display-name">
{{ currentContact.name }}
<span :class="['gender-tag', currentContact.gender]">
{{ currentContact.gender === 'm' ? '♂' : '♀' }}
</span>
</h2>
<p class="sub-text">微信号:{{ currentContact.wxid }}</p>
<p class="sub-text">地区:{{ currentContact.region }}</p>
</div>
<img :src="currentContact.avatar" class="big-avatar" />
</header>
<div class="profile-body">
<div class="info-row">
<span class="label">备注名</span>
<span class="value">{{ currentContact.alias || '未设置' }}</span>
</div>
<div class="info-row">
<span class="label">个性签名</span>
<span class="value">{{ currentContact.signature || '这个家伙很懒,什么都没留下' }}</span>
</div>
<div class="info-row">
<span class="label">来源</span>
<span class="value">通过搜索微信号添加</span>
</div>
</div>
<footer class="profile-footer">
<button class="btn-primary" @click="handleGoToChat">发消息</button>
<button class="btn-ghost">音视频通话</button>
</footer>
</div>
<div v-else class="empty-state">
<div class="empty-logo">👤</div>
<p>请在左侧选择联系人查看详情</p>
</div>
</main>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { friendService } from '@/services/friend'
const searchQuery = ref('')
const activeContactId = ref(null)
const contacts1 = ref([]);
// 模拟联系人数据
const contacts = ref([
{ id: 1, name: '南浔', wxid: 'nan_xun_99', region: '浙江 杭州', avatar: 'https://i.pravatar.cc/40?1', gender: 'f', signature: '山有木兮木有枝', alias: '南酱' },
{ id: 102, name: '老张', wxid: 'zhang_boss', region: '广东 深圳', avatar: 'https://i.pravatar.cc/40?10', gender: 'm', signature: '搞钱要紧', alias: '张总' },
{ id: 103, name: 'UI小王', wxid: 'wang_design', region: '上海 黄浦', avatar: 'https://i.pravatar.cc/40?5', gender: 'f', signature: '不改了,真的不改了', alias: '' },
{ id: 104, name: '测试组长', wxid: 'test_pro', region: '北京', avatar: 'https://i.pravatar.cc/40?8', gender: 'm', signature: 'Bug 哪里跑', alias: '铁面人' }
])
const filteredContacts = computed(() => {
return contacts.value.filter(c =>
c.name.includes(searchQuery.value) || c.wxid.includes(searchQuery.value)
)
})
const currentContact = computed(() => {
return contacts.value.find(c => c.id === activeContactId.value)
})
// 发送事件给父组件用于切换回聊天Tab并打开会话
const emit = defineEmits(['start-chat'])
function handleGoToChat() {
if (currentContact.value) {
emit('start-chat', { ...currentContact.value })
}
}
const loadContactList = async (page = 1,limit = 100) => {
const res = await friendService.getFriendList(page,limit);
contacts1.value = res.data;
}
onMounted(async () => {
await loadContactList();
})
</script>
<style scoped>
.contact-container {
display: flex;
width: 100%; /* 继承父组件的高度和宽度 */
height: 100%;
background: #fff;
}
/* --- 左侧列表栏 --- */
.contact-list-panel {
width: 250px;
background: #eee;
border-right: 1px solid #d6d6d6;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.search-section {
padding: 20px 12px 10px 12px;
}
.search-box {
display: flex;
align-items: center;
background: #dbdbdb;
padding: 5px 8px;
border-radius: 4px;
gap: 6px;
}
.search-box input {
background: transparent;
border: none;
outline: none;
font-size: 12px;
width: 100%;
}
.scroll-area {
flex: 1;
overflow-y: auto;
}
.group-title {
padding: 8px 12px;
font-size: 12px;
color: #999;
}
.list-item {
display: flex;
padding: 10px 12px;
gap: 12px;
align-items: center;
cursor: pointer;
transition: background 0.2s;
}
.list-item:hover { background: #e2e2e2; }
.list-item.active { background: #c6c6c6; }
.avatar-std {
width: 36px;
height: 36px;
border-radius: 4px;
object-fit: cover;
}
.icon-box {
width: 36px;
height: 36px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 16px;
}
.icon-box.orange { background: #faad14; }
.icon-box.green { background: #52c41a; }
.icon-box.blue { background: #1890ff; }
/* --- 右侧名片区 --- */
.profile-main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
min-width: 0;
}
.profile-card {
width: 420px;
background: transparent;
padding: 20px;
}
.profile-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding-bottom: 30px;
border-bottom: 1px solid #e7e7e7;
margin-bottom: 30px;
}
.display-name {
font-size: 24px;
color: #000;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.gender-tag { font-size: 16px; }
.gender-tag.m { color: #1890ff; }
.gender-tag.f { color: #ff4d4f; }
.sub-text {
font-size: 13px;
color: #888;
margin: 3px 0;
}
.big-avatar {
width: 70px;
height: 70px;
border-radius: 6px;
object-fit: cover;
}
.profile-body {
margin-bottom: 40px;
}
.info-row {
display: flex;
margin-bottom: 15px;
font-size: 14px;
}
.info-row .label {
width: 80px;
color: #999;
}
.info-row .value {
color: #333;
flex: 1;
}
.profile-footer {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.btn-primary {
width: 160px;
padding: 10px;
background: #07c160;
color: #fff;
border: none;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
}
.btn-ghost {
width: 160px;
padding: 10px;
background: #fff;
border: 1px solid #e0e0e0;
color: #333;
border-radius: 4px;
cursor: pointer;
}
.btn-primary:hover, .btn-ghost:hover {
opacity: 0.8;
}
.empty-state {
text-align: center;
color: #ccc;
}
.empty-logo {
font-size: 80px;
margin-bottom: 10px;
opacity: 0.2;
}
</style>