233 lines
7.5 KiB
Plaintext
233 lines
7.5 KiB
Plaintext
<!-- 商家端 - 聊天页面 -->
|
||
<template>
|
||
<view class="chat-page">
|
||
<view class="chat-header">
|
||
<view class="header-back" @click="goBack">
|
||
<text class="back-icon">‹</text>
|
||
</view>
|
||
<view class="header-info">
|
||
<text class="chat-title">{{ chatTitle }}</text>
|
||
<text class="chat-status">在线</text>
|
||
</view>
|
||
<view class="header-actions"></view>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="chat-content" :scroll-into-view="scrollToView" scroll-with-animation>
|
||
<view class="chat-messages">
|
||
<view v-for="msg in chatMessages" :key="msg.id" :class="['message-item', msg.is_from_user ? 'me' : 'received']" :id="'msg-' + msg.id">
|
||
<view v-if="!msg.is_from_user" class="message-wrapper">
|
||
<image class="avatar" src="/static/images/default-avatar.png" mode="aspectFill" />
|
||
<view class="message-content-wrapper">
|
||
<view class="message-bubble">
|
||
<text class="message-text">{{ msg.content }}</text>
|
||
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="message-wrapper me">
|
||
<view class="message-content-wrapper">
|
||
<view class="message-bubble me">
|
||
<text class="message-text">{{ msg.content }}</text>
|
||
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
|
||
</view>
|
||
</view>
|
||
<image class="avatar me" src="/static/images/default-shop.png" mode="aspectFill" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="chat-input">
|
||
<input v-model="inputText" class="input-field" placeholder="请输入消息..." confirm-type="send" @confirm="sendMessage" />
|
||
<view class="send-btn" @click="sendMessage">
|
||
<text class="send-icon">➤</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
type ChatMessageType = {
|
||
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
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
sessionId: '',
|
||
chatUserId: '',
|
||
chatTitle: '客户',
|
||
inputText: '',
|
||
chatMessages: [] as ChatMessageType[],
|
||
scrollToView: '',
|
||
merchantId: ''
|
||
}
|
||
},
|
||
|
||
onLoad(options: any) {
|
||
if (options.session_id) {
|
||
this.sessionId = options.session_id
|
||
}
|
||
if (options.user_id) {
|
||
this.chatUserId = options.user_id
|
||
}
|
||
if (options.title) {
|
||
this.chatTitle = decodeURIComponent(options.title)
|
||
}
|
||
this.initMerchantId()
|
||
},
|
||
|
||
onShow() {
|
||
this.loadChatMessages()
|
||
},
|
||
|
||
methods: {
|
||
async initMerchantId() {
|
||
try {
|
||
const session = supa.getSession()
|
||
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
||
} catch (e) {}
|
||
},
|
||
|
||
async loadChatMessages() {
|
||
try {
|
||
let query
|
||
if (this.sessionId) {
|
||
query = supa
|
||
.from('ml_chat_messages')
|
||
.select('*')
|
||
.eq('session_id', this.sessionId)
|
||
.order('created_at', { ascending: true })
|
||
} else if (this.chatUserId && this.merchantId) {
|
||
query = supa
|
||
.from('ml_chat_messages')
|
||
.select('*')
|
||
.or(`and(sender_id.eq.${this.chatUserId},receiver_id.eq.${this.merchantId}),and(sender_id.eq.${this.merchantId},receiver_id.eq.${this.chatUserId})`)
|
||
.order('created_at', { ascending: true })
|
||
}
|
||
|
||
if (query) {
|
||
const response = await query.execute()
|
||
if (response.data && (response.data as any[]).length > 0) {
|
||
const rawData = response.data as any[]
|
||
const messages: ChatMessageType[] = []
|
||
for (let i = 0; i < rawData.length; i++) {
|
||
const item = rawData[i] as UTSJSONObject
|
||
const senderId = item.getString('sender_id')
|
||
messages.push({
|
||
id: item.getString('id') || '',
|
||
session_id: item.getString('session_id') || '',
|
||
sender_id: senderId || '',
|
||
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: senderId === this.merchantId,
|
||
created_at: item.getString('created_at') || ''
|
||
} as ChatMessageType)
|
||
}
|
||
this.chatMessages = messages
|
||
this.scrollToView = messages.length > 0 ? 'msg-' + messages[messages.length - 1].id : ''
|
||
|
||
this.markAsRead()
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('加载聊天记录失败:', e)
|
||
}
|
||
},
|
||
|
||
async markAsRead() {
|
||
try {
|
||
await supa
|
||
.from('ml_chat_messages')
|
||
.update({ is_read: true })
|
||
.eq('receiver_id', this.merchantId)
|
||
.eq('is_read', false)
|
||
.execute()
|
||
} catch (e) {}
|
||
},
|
||
|
||
async sendMessage() {
|
||
if (!this.inputText.trim()) return
|
||
|
||
const content = this.inputText.trim()
|
||
this.inputText = ''
|
||
|
||
try {
|
||
const newMessage = {
|
||
session_id: this.sessionId || null,
|
||
sender_id: this.merchantId,
|
||
receiver_id: this.chatUserId,
|
||
content: content,
|
||
msg_type: 'text',
|
||
is_read: false,
|
||
is_from_user: false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_chat_messages')
|
||
.insert([newMessage])
|
||
.execute()
|
||
|
||
if (!response.error) {
|
||
this.loadChatMessages()
|
||
}
|
||
} catch (e) {
|
||
console.error('发送消息失败:', e)
|
||
}
|
||
},
|
||
|
||
goBack() {
|
||
uni.navigateBack()
|
||
},
|
||
|
||
formatTime(timeStr: string): string {
|
||
if (!timeStr) return ''
|
||
const date = new Date(timeStr)
|
||
const hours = date.getHours().toString().padStart(2, '0')
|
||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||
return `${hours}:${minutes}`
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.chat-page { display: flex; flex-direction: column; height: 100vh; background-color: #f5f5f5; }
|
||
.chat-header { display: flex; align-items: center; padding: 20rpx 30rpx; background-color: #fff; border-bottom: 1rpx solid #eee; }
|
||
.header-back { padding: 10rpx 20rpx 10rpx 0; }
|
||
.back-icon { font-size: 48rpx; color: #333; font-weight: bold; }
|
||
.header-info { flex: 1; display: flex; flex-direction: column; align-items: center; }
|
||
.chat-title { font-size: 32rpx; color: #333; font-weight: 500; }
|
||
.chat-status { font-size: 22rpx; color: #4CAF50; }
|
||
.header-actions { padding: 10rpx; }
|
||
.chat-content { flex: 1; padding: 20rpx; }
|
||
.chat-messages { display: flex; flex-direction: column; }
|
||
.message-item { margin-bottom: 30rpx; }
|
||
.message-wrapper { display: flex; align-items: flex-start; }
|
||
.message-wrapper.me { flex-direction: row-reverse; }
|
||
.avatar { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin: 0 20rpx; }
|
||
.message-content-wrapper { max-width: 70%; }
|
||
.message-bubble { background-color: #fff; padding: 20rpx; border-radius: 12rpx; position: relative; }
|
||
.message-bubble.me { background-color: #007AFF; }
|
||
.me .message-text { color: #fff; }
|
||
.me .message-time { color: rgba(255,255,255,0.7); }
|
||
.message-text { font-size: 28rpx; color: #333; line-height: 1.4; }
|
||
.message-time { display: block; font-size: 20rpx; color: #999; margin-top: 10rpx; text-align: right; }
|
||
.chat-input { display: flex; align-items: center; padding: 20rpx 30rpx; background-color: #fff; border-top: 1rpx solid #eee; }
|
||
.input-field { flex: 1; height: 72rpx; background-color: #f5f5f5; border-radius: 36rpx; padding: 0 30rpx; font-size: 28rpx; }
|
||
.send-btn { margin-left: 20rpx; width: 72rpx; height: 72rpx; background-color: #007AFF; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
|
||
.send-icon { font-size: 32rpx; color: #fff; }
|
||
</style>
|