consumer模块完成度95%,能编译在安卓端运行,在解决数据获取和页面布局问题
This commit is contained in:
232
pages/mall/merchant/chat.uvue
Normal file
232
pages/mall/merchant/chat.uvue
Normal file
@@ -0,0 +1,232 @@
|
||||
<!-- 商家端 - 聊天页面 -->
|
||||
<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>
|
||||
Reference in New Issue
Block a user