Files
hejiu/pages/chat-list/chat-list.vue
T
cg d335ec290a refactor(app): 重构应用主题和页面结构
- 移除动态主题切换功能,改为全局统一深色主题
- 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题
- 创建main容器页面统一管理三个tab页面的生命周期和状态
- 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部
- 更新页面路径从index改为main,并调整路由跳转逻辑
- 移除theme-mixin和相关主题工具函数
- 统一页面背景色设置,优化用户体验一致性
2026-09-23 20:56:14 +08:00

328 lines
7.0 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>
<view class="page-container chatlist-page">
<!-- 自定义导航栏 -->
<view class="chatlist-nav" :style="{ paddingTop: headerPaddingTop }">
<view class="chatlist-nav-inner">
<view class="nav-back" @click="goBack">
<text class="nav-back-icon">‹</text>
</view>
<text class="nav-title">消息</text>
<view class="nav-placeholder"></view>
</view>
</view>
<!-- 会话列表 -->
<scroll-view
class="chatlist-scroll"
scroll-y
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view class="chatlist-content">
<view
v-for="conv in conversations"
:key="conv.id"
class="conv-item"
@click="goChat(conv)"
>
<!-- 头像 -->
<view class="conv-avatar-wrap">
<image v-if="isValidAvatar(conv.avatar)" class="conv-avatar" :src="conv.avatar" mode="aspectFill"></image>
<view v-else class="conv-avatar conv-avatar-ph">
<text class="conv-avatar-text">{{ conv.nickname ? conv.nickname[0] : '酒' }}</text>
</view>
<view v-if="conv.online" class="conv-online"></view>
</view>
<!-- 信息 -->
<view class="conv-info">
<view class="conv-top">
<text class="conv-name">{{ conv.nickname }}</text>
<text class="conv-time">{{ conv.lastTime }}</text>
</view>
<view class="conv-bottom">
<text class="conv-last">{{ conv.lastMessage }}</text>
<view v-if="conv.unread > 0" class="conv-badge">
<text class="conv-badge-text">{{ conv.unread > 99 ? '99+' : conv.unread }}</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<EmptyState
v-if="!conversations.length && !loading"
icon="💬"
title="暂无私信"
desc="去酒友圈找个酒友聊聊吧"
actionText="去酒友圈"
@action="goCircle"
/>
</view>
</scroll-view>
</view>
</template>
<script>
import EmptyState from '../../components/EmptyState.vue'
import { GetConversations } from '../../common/api'
import wsManager from '../../common/websocket'
import { isValidAvatar } from '../../common/utils'
export default {
components: { EmptyState },
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
return {
headerPaddingTop: (menuBtn.top + 8) + 'px',
conversations: [],
loading: false,
refreshing: false
}
},
onShow() {
this.loadConversations()
this.bindWsEvents()
},
onHide() {
this.unbindWsEvents()
},
onUnload() {
this.unbindWsEvents()
},
methods: {
isValidAvatar,
async loadConversations() {
this.loading = true
try {
const res = await GetConversations()
this.conversations = res.data.list
} catch (e) {
console.warn('加载会话失败', e)
}
this.loading = false
this.refreshing = false
},
onRefresh() {
this.refreshing = true
this.loadConversations()
},
goChat(conv) {
uni.navigateTo({
url: `/pages/chat/chat?friendId=${conv.friendId}&nickname=${encodeURIComponent(conv.nickname)}&conversationId=${conv.id}`
})
},
goBack() {
uni.navigateBack()
},
goCircle() {
uni.reLaunch({ url: '/pages/main/main?tab=1' })
},
// === WebSocket 实时更新 ===
bindWsEvents() {
wsManager.on('message', this.onWsMessage)
},
unbindWsEvents() {
wsManager.off('message', this.onWsMessage)
},
/** 收到新消息时更新会话列表 */
onWsMessage(data) {
const convId = data.conversationId
const idx = this.conversations.findIndex(c => String(c.id) === String(convId))
if (idx >= 0) {
const conv = this.conversations[idx]
conv.lastMessage = data.content || '[新消息]'
conv.lastTime = '刚刚'
conv.unread = (conv.unread || 0) + 1
// 移动到顶部
this.conversations.splice(idx, 1)
this.conversations.unshift(conv)
} else {
// 新会话,重新加载列表
this.loadConversations()
}
}
}
}
</script>
<style lang="scss" scoped>
.chatlist-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.chatlist-nav {
background: $bg-base;
padding-left: $sp-lg;
padding-right: $sp-lg;
padding-bottom: $sp-md;
position: relative;
z-index: 10;
}
.chatlist-nav-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
}
.nav-back {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: $bg-card;
&:active {
background: $bg-card-alt;
}
}
.nav-back-icon {
font-size: 44rpx;
color: $text-primary;
font-weight: $fw-bold;
margin-top: -4rpx;
}
.nav-title {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $text-primary;
}
.nav-placeholder {
width: 64rpx;
}
.chatlist-scroll {
flex: 1;
height: 0;
}
.chatlist-content {
padding: $sp-md $sp-lg;
padding-bottom: calc(env(safe-area-inset-bottom) + 40rpx);
}
/* 会话项 */
.conv-item {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-lg;
background: $bg-card;
border-radius: $radius-lg;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
margin-bottom: $sp-sm;
&:active {
background: $bg-card-alt;
}
}
.conv-avatar-wrap {
position: relative;
flex-shrink: 0;
}
.conv-avatar {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
}
.conv-avatar-ph {
background: linear-gradient(135deg, rgba(232,168,56,0.15), rgba(139,133,184,0.15));
display: flex;
align-items: center;
justify-content: center;
}
.conv-avatar-text {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $amber;
}
.conv-online {
position: absolute;
bottom: 4rpx;
right: 4rpx;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: $mint;
border: 3rpx solid $bg-card;
}
.conv-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.conv-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.conv-name {
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
}
.conv-time {
font-size: $fs-xs;
color: $text-tertiary;
flex-shrink: 0;
}
.conv-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: $sp-sm;
}
.conv-last {
flex: 1;
font-size: $fs-sm;
color: $text-tertiary;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.conv-badge {
flex-shrink: 0;
min-width: 36rpx;
height: 36rpx;
padding: 0 10rpx;
border-radius: 18rpx;
background: $coral;
display: flex;
align-items: center;
justify-content: center;
}
.conv-badge-text {
font-size: 20rpx;
color: #fff;
font-weight: $fw-bold;
line-height: 1;
}
</style>