Files
hejiu/pages/chat-list/chat-list.vue
T
cg 019262289c feat(api): 升级 HaveADrink 依赖并实现用户头像上传功能
- 将 HaveADrink 依赖从 1.0.15 升级至 1.0.16 版本
- 新增 UpdateUserAvatar API 接口用于更新用户头像
- 添加 isValidAvatar 工具函数验证头像 URL 有效性
- 在登录流程中集成头像上传逻辑,支持微信头像选择
- 更新多个组件中的头像显示逻辑,过滤无效头像地址
- 优化 tabBar 主题应用逻辑,修复异步错误处理问题
- 修复微信新规范下头像选择的临时路径处理机制
2026-08-16 22:19:10 +08:00

330 lines
7.1 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="themeClass" 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 themeMixin from '../../common/theme-mixin'
import { isValidAvatar } from '../../common/utils'
export default {
mixins: [themeMixin],
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.switchTab({ url: '/pages/circle/circle' })
},
// === 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>