consumer模块完成95%,在和商家端对接聊天购物闭环
This commit is contained in:
@@ -2,12 +2,12 @@
|
||||
<template>
|
||||
<view class="chat-page">
|
||||
<!-- 聊天头部 -->
|
||||
<view class="chat-header">
|
||||
<view class="chat-header" :style="{ paddingTop: navPaddingTop }">
|
||||
<view class="header-back" @click="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<view class="header-info">
|
||||
<text class="chat-title">在线客服</text>
|
||||
<text class="chat-title">{{ headerTitle }}</text>
|
||||
<text class="chat-status">在线</text>
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
@@ -119,8 +119,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { supabaseService, type ChatMessage } from '@/utils/supabaseService.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { getCurrentUser } from '@/utils/store.uts'
|
||||
|
||||
// 响应式数据
|
||||
const messages = ref<any[]>([])
|
||||
@@ -128,64 +130,113 @@ const inputMessage = ref<string>('')
|
||||
const inputFocus = ref<boolean>(false)
|
||||
const showEmoji = ref<boolean>(false)
|
||||
const scrollToView = ref<string>('')
|
||||
const currentUserId = ref<string>('')
|
||||
const merchantId = ref<string>('') // 商家ID
|
||||
const headerTitle = ref<string>('在线客服')
|
||||
const navPaddingTop = ref<string>('30px') // 默认值,包含状态栏高度+原有内边距
|
||||
let realtimeChannel: any = null
|
||||
|
||||
// 模拟表情列表
|
||||
const emojiList = ['😊', '😂', '🤣', '😍', '😘', '🥰', '😭', '😡', '👍', '👏', '🙏', '🎉', '❤️', '🔥', '⭐']
|
||||
|
||||
// Mock 聊天记录
|
||||
const mockMessages = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'received',
|
||||
content: '您好,欢迎咨询!有什么可以帮助您的吗?',
|
||||
time: '14:30'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'sent',
|
||||
content: '你好,我昨天下的订单一直没有发货',
|
||||
time: '14:31'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'received',
|
||||
content: '请问您的订单号是多少?我帮您查询一下',
|
||||
time: '14:32'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'sent',
|
||||
content: '订单号是202311230001',
|
||||
time: '14:33'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'received',
|
||||
content: '正在为您查询订单状态...',
|
||||
time: '14:34'
|
||||
}
|
||||
]
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadChatHistory()
|
||||
onLoad((options: any) => {
|
||||
// 动态获取状态栏高度
|
||||
const sysInfo = uni.getSystemInfoSync()
|
||||
const statusBarH = sysInfo.statusBarHeight
|
||||
// 状态栏高度 + 10px 原有顶部内边距
|
||||
navPaddingTop.value = (statusBarH + 10) + 'px'
|
||||
|
||||
if (options.merchantId) {
|
||||
merchantId.value = options.merchantId
|
||||
}
|
||||
if (options.merchantName) {
|
||||
headerTitle.value = options.merchantName
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const user = await getCurrentUser()
|
||||
if (user) {
|
||||
currentUserId.value = user.id
|
||||
}
|
||||
loadChatHistory()
|
||||
setupRealtimeSubscription()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (realtimeChannel) {
|
||||
supa.removeChannel(realtimeChannel)
|
||||
}
|
||||
})
|
||||
|
||||
// 建立实时订阅
|
||||
const setupRealtimeSubscription = () => {
|
||||
console.log('开始建立聊天实时订阅...')
|
||||
realtimeChannel = supa.channel('public:ml_chat_messages')
|
||||
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'ml_chat_messages' }, (payload: any) => {
|
||||
const newMsg = payload.new
|
||||
console.log('收到新消息:', newMsg)
|
||||
|
||||
// 如果是我发的消息,因为已经乐观更新了,所以忽略(或者根据ID更新状态)
|
||||
if (newMsg.sender_id === currentUserId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是发给我的消息
|
||||
if (newMsg.receiver_id === currentUserId.value) {
|
||||
// 如果指定了商家,只接收该商家的消息
|
||||
if (merchantId.value && newMsg.sender_id !== merchantId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为UI消息格式
|
||||
const date = new Date(newMsg.created_at || new Date().toISOString())
|
||||
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
|
||||
const incomingMsg = {
|
||||
id: newMsg.id || Date.now(), // 优先使用DB ID
|
||||
type: 'received',
|
||||
content: newMsg.content,
|
||||
time: timeStr
|
||||
}
|
||||
|
||||
messages.value.push(incomingMsg)
|
||||
scrollToBottom()
|
||||
}
|
||||
})
|
||||
.subscribe((status: string) => {
|
||||
console.log('订阅状态:', status)
|
||||
})
|
||||
}
|
||||
|
||||
// 加载聊天记录
|
||||
const loadChatHistory = async () => {
|
||||
const rawMsgs = await supabaseService.getUserChatMessages()
|
||||
let rawMsgs: ChatMessage[] = []
|
||||
|
||||
if (merchantId.value) {
|
||||
rawMsgs = await supabaseService.getChatMessages(merchantId.value)
|
||||
} else {
|
||||
rawMsgs = await supabaseService.getUserChatMessages()
|
||||
}
|
||||
|
||||
messages.value = rawMsgs.reverse().map((m: ChatMessage) => {
|
||||
const date = new Date(m.created_at || new Date().toISOString())
|
||||
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
|
||||
// Use explicit 'as' casting to avoid type errors if needed, though map handles it
|
||||
const msg : any = {
|
||||
id: m.id,
|
||||
type: m.is_from_user ? 'sent' : 'received',
|
||||
type: m.is_from_user ? 'sent' : 'received', // 假设is_from_user标志是准确的,或者比较 sender_id
|
||||
content: m.content,
|
||||
time: timeStr
|
||||
}
|
||||
// 双重确认类型
|
||||
if (currentUserId.value && m.sender_id === currentUserId.value) {
|
||||
msg.type = 'sent'
|
||||
} else if (currentUserId.value && m.sender_id !== currentUserId.value) {
|
||||
msg.type = 'received'
|
||||
}
|
||||
|
||||
return msg
|
||||
})
|
||||
|
||||
@@ -200,7 +251,7 @@ const sendMessage = async () => {
|
||||
const content = inputMessage.value.trim()
|
||||
if (!content) return
|
||||
|
||||
// 添加发送的消息
|
||||
// 添加发送的消息 (乐观更新)
|
||||
const newMessage = {
|
||||
id: Date.now(),
|
||||
type: 'sent',
|
||||
@@ -214,44 +265,34 @@ const sendMessage = async () => {
|
||||
// 滚动到底部
|
||||
scrollToBottom()
|
||||
|
||||
// Backend Save
|
||||
await supabaseService.sendChatMessage(content)
|
||||
// 发送到 Supabase
|
||||
// 如果有 merchantId,发送给指定商家,否则可能是发给系统或默认客服
|
||||
const success = await supabaseService.sendChatMessage(content, merchantId.value || null)
|
||||
|
||||
// 模拟客服回复(2秒后)
|
||||
if (!success) {
|
||||
uni.showToast({
|
||||
title: '发送失败',
|
||||
icon: 'none'
|
||||
})
|
||||
// 实际项目中可能需要标记消息为发送失败状态
|
||||
}
|
||||
|
||||
// 移除模拟回复,依赖 Realtime 接收真实回复
|
||||
/*
|
||||
setTimeout(() => {
|
||||
simulateCustomerReply()
|
||||
}, 2000)
|
||||
*/
|
||||
}
|
||||
|
||||
// 模拟客服回复
|
||||
// 模拟客服回复 (已禁用,改用 Realtime)
|
||||
/*
|
||||
const simulateCustomerReply = async () => {
|
||||
const replies = [
|
||||
'好的,已为您记录',
|
||||
'这个问题需要进一步核实',
|
||||
'我明白了,马上为您处理',
|
||||
'请稍等,正在为您查询',
|
||||
'感谢您的反馈'
|
||||
]
|
||||
|
||||
const randomReply = replies[Math.floor(Math.random() * replies.length)]
|
||||
|
||||
await supabaseService.simulateServiceReply(randomReply)
|
||||
|
||||
addReceivedMessage(randomReply)
|
||||
// ...
|
||||
}
|
||||
*/
|
||||
|
||||
// 添加接收的消息
|
||||
const addReceivedMessage = (content: string) => {
|
||||
const newMessage = {
|
||||
id: Date.now(),
|
||||
type: 'received',
|
||||
content: content,
|
||||
time: getCurrentTime()
|
||||
}
|
||||
|
||||
messages.value.push(newMessage)
|
||||
scrollToBottom()
|
||||
}
|
||||
/* 移除不再使用的 simulateCustomerReply 和 addReceivedMessage */
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
@@ -358,7 +399,9 @@ const goBack = () => {
|
||||
.chat-header {
|
||||
background-color: white;
|
||||
padding: 10px 15px;
|
||||
/* padding-top 由内联样式控制 */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #eee;
|
||||
@@ -437,6 +480,7 @@ const goBack = () => {
|
||||
/* 消息项 */
|
||||
.message-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@@ -517,6 +561,7 @@ const goBack = () => {
|
||||
|
||||
.input-tools {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -528,6 +573,7 @@ const goBack = () => {
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -573,6 +619,7 @@ const goBack = () => {
|
||||
|
||||
.emoji-category {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user