index.vue 9.07 KB
<template>
    <view class="messages-page">
        <!-- 顶部搜索栏 -->
        <view class="search-container">
            <view class="search-box">
                <Search size="18" color="#9ca3af" />
                <input 
                    v-model="searchValue" 
                    placeholder="搜索聊天记录..." 
                    class="search-input"
                />
            </view>
        </view>

        <!-- 消息列表 -->
        <view class="messages-list">
            <view 
                v-for="message in filteredMessages" 
                :key="message.id"
                class="message-item"
                @click="onMessageClick(message)"
            >
                <view class="avatar-container">
                    <image :src="message.avatar" class="avatar" mode="aspectFill" />
                    <view v-if="message.unreadCount > 0" class="unread-badge">
                        <text class="unread-count">{{ message.unreadCount > 99 ? '99+' : message.unreadCount }}</text>
                    </view>
                </view>
                
                <view class="message-content">
                    <view class="message-header">
                        <text class="sender-name">{{ message.senderName }}</text>
                        <text class="message-time">{{ formatTime(message.timestamp) }}</text>
                    </view>
                    
                    <view class="message-preview">
                        <text class="preview-text" :class="{ 'unread': message.unreadCount > 0 }">
                            {{ message.lastMessage }}
                        </text>
                        <view v-if="message.type === 'image'" class="message-type-icon">
                            <Image size="16" color="#9ca3af" />
                        </view>
                    </view>
                </view>
            </view>
        </view>

        <!-- 空状态 -->
        <view v-if="filteredMessages.length === 0" class="empty-state">
            <view class="empty-icon">
                <Message size="48" color="#d1d5db" />
            </view>
            <text class="empty-title">暂无消息</text>
            <text class="empty-subtitle">开始与买家或卖家聊天吧</text>
        </view>

        <!-- 浮动按钮 -->
        <view class="floating-btn" @click="onNewMessage">
            <Plus size="24" color="#ffffff" />
        </view>
        
        <!-- 自定义TabBar -->
        <TabBar />
    </view>
</template>

<script setup>
import { ref, computed } from 'vue'
import { Search, Message, Plus, Image } from '@nutui/icons-vue-taro'
import Taro from '@tarojs/taro'
import TabBar from '@/components/TabBar.vue'

// 响应式数据
const searchValue = ref('')

// 消息数据
const messages = ref([
    {
        id: 1,
        senderName: '张同学',
        avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face',
        lastMessage: '这辆车还在吗?可以看看实物吗?',
        timestamp: Date.now() - 300000, // 5分钟前
        unreadCount: 2,
        type: 'text'
    },
    {
        id: 2,
        senderName: '李小明',
        avatar: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?w=100&h=100&fit=crop&crop=face',
        lastMessage: '价格还能再便宜点吗?',
        timestamp: Date.now() - 1800000, // 30分钟前
        unreadCount: 0,
        type: 'text'
    },
    {
        id: 3,
        senderName: '王美丽',
        avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face',
        lastMessage: '[图片]',
        timestamp: Date.now() - 3600000, // 1小时前
        unreadCount: 1,
        type: 'image'
    },
    {
        id: 4,
        senderName: '陈大华',
        avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face',
        lastMessage: '好的,谢谢!',
        timestamp: Date.now() - 7200000, // 2小时前
        unreadCount: 0,
        type: 'text'
    },
    {
        id: 5,
        senderName: '刘小红',
        avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face',
        lastMessage: '车子的电池还好用吗?大概能跑多远?',
        timestamp: Date.now() - 86400000, // 1天前
        unreadCount: 0,
        type: 'text'
    },
    {
        id: 6,
        senderName: '赵强',
        avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face',
        lastMessage: '明天下午有时间看车吗?',
        timestamp: Date.now() - 172800000, // 2天前
        unreadCount: 0,
        type: 'text'
    }
])

// 过滤后的消息列表
const filteredMessages = computed(() => {
    if (!searchValue.value.trim()) {
        return messages.value
    }
    
    return messages.value.filter(message => 
        message.senderName.includes(searchValue.value) ||
        message.lastMessage.includes(searchValue.value)
    )
})

/**
 * 格式化时间
 * @param {number} timestamp - 时间戳
 * @returns {string} 格式化后的时间
 */
const formatTime = (timestamp) => {
    const now = Date.now()
    const diff = now - timestamp
    
    if (diff < 60000) { // 1分钟内
        return '刚刚'
    } else if (diff < 3600000) { // 1小时内
        return `${Math.floor(diff / 60000)}分钟前`
    } else if (diff < 86400000) { // 1天内
        return `${Math.floor(diff / 3600000)}小时前`
    } else if (diff < 604800000) { // 1周内
        return `${Math.floor(diff / 86400000)}天前`
    } else {
        const date = new Date(timestamp)
        return `${date.getMonth() + 1}/${date.getDate()}`
    }
}

/**
 * 消息点击事件
 * @param {object} message - 消息对象
 */
const onMessageClick = (message) => {
    // 清除未读数量
    message.unreadCount = 0
    
    // 跳转到聊天详情页面
    Taro.navigateTo({
        url: `/pages/chat/index?userId=${message.id}&userName=${message.senderName}`
    })
}

/**
 * 新建消息
 */
const onNewMessage = async () => {
    try {
        await Taro.showToast({
            title: '新建消息',
            icon: 'none'
        })
    } catch (error) {
        console.error('新建消息失败:', error)
    }
}
</script>

<style lang="less">
.messages-page {
    min-height: 100vh;
    background-color: #f9fafb;
    padding-bottom: 100px;
}

.search-container {
    padding: 16px;
    background-color: #ffffff;
    border-bottom: 1px solid #f3f4f6;
}

.search-box {
    display: flex;
    align-items: center;
    background-color: #f9fafb;
    border-radius: 24px;
    padding: 12px 16px;
    gap: 8px;
}

.search-input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    font-size: 14px;
    color: #374151;
}

.messages-list {
    background-color: #ffffff;
}

.message-item {
    display: flex;
    align-items: center;
    padding: 16px;
    border-bottom: 1px solid #f3f4f6;
    transition: background-color 0.2s;
}

.message-item:active {
    background-color: #f9fafb;
}

.message-item:last-child {
    border-bottom: none;
}

.avatar-container {
    position: relative;
    margin-right: 12px;
}

.avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
}

.unread-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 20px;
    height: 20px;
    background-color: #ef4444;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid #ffffff;
}

.unread-count {
    font-size: 12px;
    color: #ffffff;
    font-weight: 500;
    line-height: 1;
}

.message-content {
    flex: 1;
    min-width: 0;
}

.message-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 4px;
}

.sender-name {
    font-size: 16px;
    font-weight: 500;
    color: #111827;
}

.message-time {
    font-size: 12px;
    color: #9ca3af;
}

.message-preview {
    display: flex;
    align-items: center;
    gap: 4px;
}

.preview-text {
    flex: 1;
    font-size: 14px;
    color: #6b7280;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.preview-text.unread {
    color: #374151;
    font-weight: 500;
}

.message-type-icon {
    flex-shrink: 0;
}

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 80px 20px;
    text-align: center;
}

.empty-icon {
    margin-bottom: 16px;
}

.empty-title {
    font-size: 18px;
    font-weight: 500;
    color: #374151;
    margin-bottom: 8px;
    display: block;
}

.empty-subtitle {
    font-size: 14px;
    color: #9ca3af;
    display: block;
}

.floating-btn {
    position: fixed;
    bottom: 100px;
    right: 20px;
    width: 56px;
    height: 56px;
    background-color: #f97316;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
    transition: all 0.2s;
}

.floating-btn:active {
    transform: scale(0.95);
    box-shadow: 0 2px 8px rgba(249, 115, 22, 0.4);
}
</style>