248 lines
9.2 KiB
Plaintext
248 lines
9.2 KiB
Plaintext
<!-- 商家端 - 消息中心页面 -->
|
|
<template>
|
|
<view class="messages-page">
|
|
<view class="tabs">
|
|
<view class="tab" :class="{ active: currentTab === 'chat' }" @click="switchTab('chat')">会话列表</view>
|
|
<view class="tab" :class="{ active: currentTab === 'all' }" @click="switchTab('all')">全部消息</view>
|
|
</view>
|
|
|
|
<scroll-view class="messages-list" scroll-y :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
|
<view v-if="loading && conversations.length === 0" class="loading-container"><text class="loading-text">加载中...</text></view>
|
|
<view v-else-if="currentTab === 'chat' && conversations.length === 0" class="empty-container"><text class="empty-icon">💬</text><text class="empty-text">暂无会话</text></view>
|
|
<view v-else-if="currentTab === 'all' && messages.length === 0" class="empty-container"><text class="empty-icon">📭</text><text class="empty-text">暂无消息</text></view>
|
|
|
|
<!-- 会话列表 -->
|
|
<view v-else-if="currentTab === 'chat'">
|
|
<view v-for="conv in conversations" :key="conv.sessionId" class="conversation-card" @click="goToChat(conv)">
|
|
<image class="conv-avatar" :src="conv.avatar || '/static/images/default-avatar.png'" mode="aspectFill" />
|
|
<view class="conv-info">
|
|
<view class="conv-header">
|
|
<text class="conv-name">{{ conv.name }}</text>
|
|
<text class="conv-time">{{ conv.lastTime }}</text>
|
|
</view>
|
|
<text class="conv-preview">{{ conv.lastMessage }}</text>
|
|
</view>
|
|
<view v-if="conv.unread > 0" class="unread-badge"><text>{{ conv.unread > 99 ? '99+' : conv.unread }}</text></view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 全部消息 -->
|
|
<view v-else>
|
|
<view v-for="msg in messages" :key="msg.id" class="message-card" :class="{ unread: !msg.is_read }" @click="viewMessage(msg)">
|
|
<view class="message-icon">{{ msg.is_from_user ? '👤' : '🏪' }}</view>
|
|
<view class="message-content">
|
|
<view class="message-header">
|
|
<text class="message-title">{{ msg.is_from_user ? '发给客户' : '收到消息' }}</text>
|
|
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
|
|
</view>
|
|
<text class="message-text">{{ msg.content }}</text>
|
|
</view>
|
|
<view v-if="!msg.is_read" class="unread-dot"></view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="uts">
|
|
import supa from '@/components/supadb/aksupainstance.uts'
|
|
|
|
type MessageType = {
|
|
id: string
|
|
session_id: string
|
|
sender_id: string
|
|
receiver_id: string
|
|
content: string
|
|
msg_type: string
|
|
is_read: boolean
|
|
is_from_user: boolean
|
|
created_at: string
|
|
}
|
|
|
|
type ConversationType = {
|
|
sessionId: string
|
|
name: string
|
|
avatar: string
|
|
lastMessage: string
|
|
lastTime: string
|
|
unread: number
|
|
userId: string
|
|
}
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
currentTab: 'chat',
|
|
messages: [] as MessageType[],
|
|
conversations: [] as ConversationType[],
|
|
loading: false,
|
|
refreshing: false,
|
|
merchantId: ''
|
|
}
|
|
},
|
|
|
|
onLoad() {
|
|
this.initMerchantId()
|
|
},
|
|
|
|
onShow() {
|
|
this.loadMessages()
|
|
},
|
|
|
|
methods: {
|
|
async initMerchantId() {
|
|
try {
|
|
const session = supa.getSession()
|
|
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
|
} catch (e) {}
|
|
},
|
|
|
|
async loadMessages() {
|
|
this.loading = true
|
|
|
|
try {
|
|
const query = supa
|
|
.from('ml_chat_messages')
|
|
.select('*')
|
|
.or(`receiver_id.eq.${this.merchantId},sender_id.eq.${this.merchantId}`)
|
|
.order('created_at', { ascending: false })
|
|
.limit(100)
|
|
|
|
const response = await query.execute()
|
|
|
|
if (response.error != null || !response.data) {
|
|
this.messages = []
|
|
this.conversations = []
|
|
return
|
|
}
|
|
|
|
const rawData = response.data as any[]
|
|
const messagesData: MessageType[] = []
|
|
const sessionMap = new Map<string, ConversationType>()
|
|
|
|
for (let i = 0; i < rawData.length; i++) {
|
|
const item = rawData[i] as UTSJSONObject
|
|
const msg: MessageType = {
|
|
id: item.getString('id') || '',
|
|
session_id: item.getString('session_id') || '',
|
|
sender_id: item.getString('sender_id') || '',
|
|
receiver_id: item.getString('receiver_id') || '',
|
|
content: item.getString('content') || '',
|
|
msg_type: item.getString('msg_type') || 'text',
|
|
is_read: item.getBoolean('is_read') || false,
|
|
is_from_user: item.getBoolean('is_from_user') || false,
|
|
created_at: item.getString('created_at') || ''
|
|
}
|
|
messagesData.push(msg)
|
|
|
|
const otherUserId = msg.is_from_user ? msg.receiver_id : msg.sender_id
|
|
const sessionId = msg.session_id || otherUserId
|
|
|
|
if (!sessionMap.has(sessionId)) {
|
|
sessionMap.set(sessionId, {
|
|
sessionId: sessionId,
|
|
name: '客户',
|
|
avatar: '',
|
|
lastMessage: msg.content,
|
|
lastTime: this.formatTime(msg.created_at),
|
|
unread: 0,
|
|
userId: otherUserId
|
|
})
|
|
}
|
|
|
|
const conv = sessionMap.get(sessionId)!
|
|
conv.lastMessage = msg.content
|
|
conv.lastTime = this.formatTime(msg.created_at)
|
|
if (!msg.is_read && !msg.is_from_user) {
|
|
conv.unread++
|
|
}
|
|
}
|
|
|
|
this.messages = messagesData
|
|
this.conversations = Array.from(sessionMap.values()).sort((a, b) => b.unread - a.unread)
|
|
|
|
} catch (e) {
|
|
console.error('加载消息失败:', e)
|
|
} finally {
|
|
this.loading = false
|
|
this.refreshing = false
|
|
}
|
|
},
|
|
|
|
goToChat(conv: ConversationType) {
|
|
uni.navigateTo({
|
|
url: `/pages/mall/merchant/chat?user_id=${conv.userId}&session_id=${conv.sessionId}&title=${encodeURIComponent(conv.name)}`
|
|
})
|
|
},
|
|
|
|
switchTab(tab: string) {
|
|
this.currentTab = tab
|
|
},
|
|
|
|
onRefresh() {
|
|
this.refreshing = true
|
|
this.loadMessages()
|
|
},
|
|
|
|
viewMessage(msg: MessageType) {
|
|
if (!msg.is_read) {
|
|
supa.from('ml_chat_messages').update({ is_read: true }).eq('id', msg.id).execute()
|
|
msg.is_read = true
|
|
}
|
|
|
|
const otherUserId = msg.is_from_user ? msg.receiver_id : msg.sender_id
|
|
uni.navigateTo({
|
|
url: `/pages/mall/merchant/chat?user_id=${otherUserId}&session_id=${msg.session_id}`
|
|
})
|
|
},
|
|
|
|
formatTime(timeStr: string): string {
|
|
if (!timeStr) return ''
|
|
const date = new Date(timeStr)
|
|
const now = new Date()
|
|
const diff = now.getTime() - date.getTime()
|
|
const hours = Math.floor(diff / (1000 * 60 * 60))
|
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
|
|
|
if (hours < 1) return '刚刚'
|
|
if (hours < 24) return `${hours}小时前`
|
|
if (days === 1) return '昨天'
|
|
if (days < 7) return `${days}天前`
|
|
return `${date.getMonth() + 1}-${date.getDate()}`
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.messages-page { background-color: #f5f5f5; min-height: 100vh; }
|
|
.tabs { display: flex; background-color: #fff; padding: 0 20rpx; position: sticky; top: 0; z-index: 10; }
|
|
.tab { flex: 1; text-align: center; padding: 24rpx 0; font-size: 28rpx; color: #666; position: relative; }
|
|
.tab.active { color: #007AFF; font-weight: bold; }
|
|
.tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40rpx; height: 4rpx; background-color: #007AFF; border-radius: 2rpx; }
|
|
.messages-list { padding: 20rpx; height: calc(100vh - 100rpx); }
|
|
.loading-container, .empty-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100rpx 0; }
|
|
.empty-icon { font-size: 100rpx; margin-bottom: 20rpx; }
|
|
.empty-text, .loading-text { font-size: 28rpx; color: #999; }
|
|
|
|
.conversation-card { display: flex; align-items: center; background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; position: relative; }
|
|
.conv-avatar { width: 100rpx; height: 100rpx; border-radius: 12rpx; margin-right: 20rpx; }
|
|
.conv-info { flex: 1; }
|
|
.conv-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10rpx; }
|
|
.conv-name { font-size: 30rpx; color: #333; font-weight: 500; }
|
|
.conv-time { font-size: 22rpx; color: #999; }
|
|
.conv-preview { font-size: 26rpx; color: #666; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
|
.unread-badge { min-width: 36rpx; height: 36rpx; background-color: #FF3B30; border-radius: 18rpx; display: flex; align-items: center; justify-content: center; padding: 0 10rpx; }
|
|
.unread-badge text { font-size: 20rpx; color: #fff; }
|
|
|
|
.message-card { display: flex; align-items: flex-start; background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; position: relative; }
|
|
.message-card.unread { background-color: #f0f9ff; }
|
|
.message-icon { font-size: 40rpx; margin-right: 20rpx; }
|
|
.message-content { flex: 1; }
|
|
.message-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10rpx; }
|
|
.message-title { font-size: 28rpx; color: #333; font-weight: 500; }
|
|
.message-time { font-size: 22rpx; color: #999; }
|
|
.message-text { font-size: 26rpx; color: #666; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
.unread-dot { position: absolute; top: 30rpx; right: 30rpx; width: 16rpx; height: 16rpx; background-color: #FF3B30; border-radius: 50%; }
|
|
</style>
|