consumer模块完成度95%,准备部署消费者端测试
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
<!-- pages/mall/consumer/chat.uvue -->
|
||||
<!-- pages/mall/consumer/chat.uvue -->
|
||||
<template>
|
||||
<view class="chat-page">
|
||||
<!-- 聊天头部 -->
|
||||
<view class="chat-header" :style="{ paddingTop: navPaddingTop }">
|
||||
<view class="header-back" @click="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
<text class="back-icon">🔙</text>
|
||||
</view>
|
||||
<view class="header-info">
|
||||
<text class="chat-title">{{ headerTitle }}</text>
|
||||
<text class="chat-status">在线</text>
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
<text class="action-icon" @click="showMoreActions">⋮</text>
|
||||
<text class="action-icon" @click="showMoreActions">⋯</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 聊天输入区 -->
|
||||
<!-- 聊天输入框 -->
|
||||
<view class="chat-input">
|
||||
<view class="input-tools">
|
||||
<text class="tool-icon" @click="showEmojiPicker">😊</text>
|
||||
@@ -129,11 +129,11 @@ import type { AkSupaRealtimeChannel } from '@/components/supadb/aksupa.uts'
|
||||
import { getCurrentUser } from '@/utils/store.uts'
|
||||
|
||||
type UiChatMessage = {
|
||||
id: string
|
||||
viewId: string
|
||||
type: string
|
||||
content: string
|
||||
time: string
|
||||
id: string
|
||||
viewId: string
|
||||
type: string
|
||||
content: string
|
||||
time: string
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
@@ -151,7 +151,7 @@ const isInitialLoading = ref<boolean>(true)
|
||||
let realtimeChannel: AkSupaRealtimeChannel | null = null
|
||||
|
||||
// 模拟表情列表
|
||||
const emojiList = ['😊', '😂', '🤣', '😍', '😘', '🥰', '😭', '😡', '👍', '👏', '🙏', '🎉', '❤️', '🔥', '⭐']
|
||||
const emojiList = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
|
||||
|
||||
function scrollToBottom() : void {
|
||||
if (messages.value.length === 0) return
|
||||
@@ -161,13 +161,13 @@ function scrollToBottom() : void {
|
||||
const targetId = 'msg-' + lastMsg.id
|
||||
|
||||
// 关键点:在 UVue 安卓端,直接连续赋值可能被合并。
|
||||
// 我们先清除 ID,然后在下一帧赋值,确保 scroll-view 监听到变化。
|
||||
// 我们先清空 ID,然后在下一帧赋值,确保 scroll-view 监听到变化。
|
||||
scrollToView.value = ''
|
||||
|
||||
// 增加多次尝试,确保在 DOM 彻底完成渲染(包含由于高度计算引起的多次排版)后定位。
|
||||
setTimeout(() => {
|
||||
scrollToView.value = targetId
|
||||
console.log('[scrollToBottom] 发起第一次滚动定位:', targetId)
|
||||
console.log('[scrollToBottom] 发起第一次滚动定位', targetId)
|
||||
|
||||
// 二次校准:针对长消息或图片导致的高度变化
|
||||
setTimeout(() => {
|
||||
@@ -184,98 +184,98 @@ function getCurrentTime(): string {
|
||||
const now = new Date()
|
||||
const hours = now.getHours().toString().padStart(2, '0')
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0')
|
||||
return `${hours}:${minutes}`
|
||||
return ${hours}:
|
||||
}
|
||||
|
||||
function setupRealtimeSubscription(): void {
|
||||
console.log('开始建立聊天实时订阅...')
|
||||
console.log('当前用户ID:', currentUserId.value, '商家ID:', merchantId.value)
|
||||
|
||||
realtimeChannel = supa.channel('chat-messages-' + Date.now().toString())
|
||||
.on('postgres_changes', {
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'ml_chat_messages'
|
||||
}, (payload: any) => {
|
||||
console.log('=== 收到实时订阅回调 ===')
|
||||
const payloadObj = (payload instanceof UTSJSONObject) ? (payload as UTSJSONObject) : (JSON.parse(JSON.stringify(payload ?? {})) as UTSJSONObject)
|
||||
const newMsgAny = payloadObj.get('new')
|
||||
if (newMsgAny == null) {
|
||||
console.log('newMsgAny 为空,跳过')
|
||||
return
|
||||
}
|
||||
const newMsg = (newMsgAny instanceof UTSJSONObject) ? (newMsgAny as UTSJSONObject) : (JSON.parse(JSON.stringify(newMsgAny)) as UTSJSONObject)
|
||||
console.log('收到新消息:', newMsg)
|
||||
console.log('开始建立聊天实时订阅...')
|
||||
console.log('当前用户ID:', currentUserId.value, '商家ID:', merchantId.value)
|
||||
|
||||
const senderId = newMsg.getString('sender_id') ?? ''
|
||||
const receiverId = newMsg.getString('receiver_id') ?? ''
|
||||
const msgId = newMsg.getString('id') ?? ''
|
||||
const content = newMsg.getString('content') ?? ''
|
||||
|
||||
console.log('=== 消息详情 ===')
|
||||
console.log('消息ID:', msgId)
|
||||
console.log('发送者ID:', senderId)
|
||||
console.log('接收者ID:', receiverId)
|
||||
console.log('当前用户ID:', currentUserId.value)
|
||||
console.log('商家ID:', merchantId.value)
|
||||
console.log('消息内容:', content)
|
||||
realtimeChannel = supa.channel('chat-messages-' + Date.now().toString())
|
||||
.on('postgres_changes', {
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'ml_chat_messages'
|
||||
}, (payload: any) => {
|
||||
console.log('=== 收到实时订阅回调 ===')
|
||||
const payloadObj = (payload instanceof UTSJSONObject) ? (payload as UTSJSONObject) : (JSON.parse(JSON.stringify(payload ?? {})) as UTSJSONObject)
|
||||
const newMsgAny = payloadObj.get('new')
|
||||
if (newMsgAny == null) {
|
||||
console.log('newMsgAny 为空,跳过')
|
||||
return
|
||||
}
|
||||
const newMsg = (newMsgAny instanceof UTSJSONObject) ? (newMsgAny as UTSJSONObject) : (JSON.parse(JSON.stringify(newMsgAny)) as UTSJSONObject)
|
||||
console.log('收到新消息', newMsg)
|
||||
|
||||
// 检查消息是否已经在列表中(避免重复)
|
||||
for (let i = 0; i < messages.value.length; i++) {
|
||||
if (messages.value[i].id == msgId) {
|
||||
console.log('消息已存在,跳过')
|
||||
return
|
||||
}
|
||||
}
|
||||
const senderId = newMsg.getString('sender_id') ?? ''
|
||||
const receiverId = newMsg.getString('receiver_id') ?? ''
|
||||
const msgId = newMsg.getString('id') ?? ''
|
||||
const content = newMsg.getString('content') ?? ''
|
||||
|
||||
// 判断消息类型
|
||||
const isMyMessage = (senderId == currentUserId.value)
|
||||
const isForMe = (receiverId == currentUserId.value)
|
||||
const isRelatedToCurrentChat = (senderId == merchantId.value || receiverId == merchantId.value)
|
||||
|
||||
console.log('=== 条件判断 ===')
|
||||
console.log('isMyMessage:', isMyMessage)
|
||||
console.log('isForMe:', isForMe)
|
||||
console.log('isRelatedToCurrentChat:', isRelatedToCurrentChat)
|
||||
console.log('=== 消息详情 ===')
|
||||
console.log('消息ID:', msgId)
|
||||
console.log('发送者ID:', senderId)
|
||||
console.log('接收者ID:', receiverId)
|
||||
console.log('当前用户ID:', currentUserId.value)
|
||||
console.log('商家ID:', merchantId.value)
|
||||
console.log('消息内容:', content)
|
||||
|
||||
// 如果消息与当前聊天无关,跳过
|
||||
if (!isRelatedToCurrentChat) {
|
||||
console.log('消息与当前聊天无关,跳过')
|
||||
return
|
||||
}
|
||||
// 检查消息是否已经在列表中(避免重复)
|
||||
for (let i = 0; i < messages.value.length; i++) {
|
||||
if (messages.value[i].id == msgId) {
|
||||
console.log('消息已存在,跳过')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是自己发送的消息,或者是发给自己的消息,都显示
|
||||
if (isMyMessage || isForMe) {
|
||||
const createdAt = newMsg.getString('created_at') ?? new Date().toISOString()
|
||||
const date = new Date(createdAt)
|
||||
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
// 判断消息类型
|
||||
const isMyMessage = (senderId == currentUserId.value)
|
||||
const isForMe = (receiverId == currentUserId.value)
|
||||
const isRelatedToCurrentChat = (senderId == merchantId.value || receiverId == merchantId.value)
|
||||
|
||||
// 生成安全的 viewId
|
||||
const safeViewId = 'msg_' + msgId.replace(/[^a-zA-Z0-9]/g, '_')
|
||||
console.log('=== 条件判断 ===')
|
||||
console.log('isMyMessage:', isMyMessage)
|
||||
console.log('isForMe:', isForMe)
|
||||
console.log('isRelatedToCurrentChat:', isRelatedToCurrentChat)
|
||||
|
||||
const incomingMsg: UiChatMessage = {
|
||||
id: msgId,
|
||||
viewId: safeViewId,
|
||||
type: isMyMessage ? 'sent' : 'received',
|
||||
content: content,
|
||||
time: timeStr
|
||||
}
|
||||
// 如果消息与当前聊天无关,跳过
|
||||
if (!isRelatedToCurrentChat) {
|
||||
console.log('消息与当前聊天无关,跳过')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('=== 添加新消息到列表 ===')
|
||||
console.log('消息类型:', incomingMsg.type)
|
||||
console.log('消息内容:', incomingMsg.content)
|
||||
messages.value.push(incomingMsg)
|
||||
scrollToBottom()
|
||||
} else {
|
||||
console.log('条件不满足,不添加消息')
|
||||
}
|
||||
})
|
||||
.subscribe((status: string, err: any | null) => {
|
||||
console.log('订阅状态:', status)
|
||||
if (err != null) {
|
||||
console.log('订阅错误:', err)
|
||||
}
|
||||
})
|
||||
// 如果是自己发送的消息,或者是发给自己的消息,都显示
|
||||
if (isMyMessage || isForMe) {
|
||||
const createdAt = newMsg.getString('created_at') ?? new Date().toISOString()
|
||||
const date = new Date(createdAt)
|
||||
const timeStr = ${date.getHours().toString().padStart(2, '0')}:
|
||||
|
||||
// 生成安全从 viewId
|
||||
const safeViewId = 'msg_' + msgId.replace(/[^a-zA-Z0-9]/g, '_')
|
||||
|
||||
const incomingMsg: UiChatMessage = {
|
||||
id: msgId,
|
||||
viewId: safeViewId,
|
||||
type: isMyMessage ? 'sent' : 'received',
|
||||
content: content,
|
||||
time: timeStr
|
||||
}
|
||||
|
||||
console.log('=== 添加新消息到列表 ===')
|
||||
console.log('消息类型:', incomingMsg.type)
|
||||
console.log('消息内容:', incomingMsg.content)
|
||||
messages.value.push(incomingMsg)
|
||||
scrollToBottom()
|
||||
} else {
|
||||
console.log('条件不满足,不添加消息')
|
||||
}
|
||||
})
|
||||
.subscribe((status: string, err: any | null) => {
|
||||
console.log('订阅状态', status)
|
||||
if (err != null) {
|
||||
console.log('订阅错误:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadChatHistory(): Promise<void> {
|
||||
@@ -300,7 +300,7 @@ async function loadChatHistory(): Promise<void> {
|
||||
for (let i = 0; i < sortedRawMsgs.length; i++) {
|
||||
const m = sortedRawMsgs[i]
|
||||
const date = new Date(m.created_at ?? new Date().toISOString())
|
||||
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
const timeStr = ${date.getHours().toString().padStart(2, '0')}:
|
||||
|
||||
const sender = m.sender_id ?? ''
|
||||
const msgType = (currentUserId.value != '' && sender == currentUserId.value) ? 'sent' : 'received'
|
||||
@@ -333,42 +333,42 @@ function onScrollToUpper(e: any): void {
|
||||
}
|
||||
|
||||
async function loadMerchantInfo(): Promise<void> {
|
||||
if (merchantId.value == '') return
|
||||
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_shops')
|
||||
.select('shop_logo, shop_name')
|
||||
.eq('merchant_id', merchantId.value)
|
||||
.limit(1)
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('[loadMerchantInfo] 获取商家信息失败:', response.error)
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data
|
||||
if (rawData == null) return
|
||||
|
||||
const rawList = rawData as any[]
|
||||
if (rawList.length == 0) return
|
||||
|
||||
const shopData = rawList[0]
|
||||
const shopObj = JSON.parse(JSON.stringify(shopData)) as UTSJSONObject
|
||||
|
||||
const logo = shopObj.getString('shop_logo')
|
||||
if (logo != null && logo != '') {
|
||||
merchantAvatar.value = logo
|
||||
}
|
||||
|
||||
const name = shopObj.getString('shop_name')
|
||||
if (name != null && name != '' && headerTitle.value == '在线客服') {
|
||||
headerTitle.value = name
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[loadMerchantInfo] 获取商家信息异常:', e)
|
||||
}
|
||||
if (merchantId.value == '') return
|
||||
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_shops')
|
||||
.select('shop_logo, shop_name')
|
||||
.eq('merchant_id', merchantId.value)
|
||||
.limit(1)
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('[loadMerchantInfo] 获取商家信息失败:', response.error)
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data
|
||||
if (rawData == null) return
|
||||
|
||||
const rawList = rawData as any[]
|
||||
if (rawList.length == 0) return
|
||||
|
||||
const shopData = rawList[0]
|
||||
const shopObj = JSON.parse(JSON.stringify(shopData)) as UTSJSONObject
|
||||
|
||||
const logo = shopObj.getString('shop_logo')
|
||||
if (logo != null && logo != '') {
|
||||
merchantAvatar.value = logo
|
||||
}
|
||||
|
||||
const name = shopObj.getString('shop_name')
|
||||
if (name != null && name != '' && headerTitle.value == '在线客服') {
|
||||
headerTitle.value = name
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[loadMerchantInfo] 获取商家信息异常:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
@@ -379,33 +379,33 @@ onLoad((options: any) => {
|
||||
// 状态栏高度 + 10px 原有顶部内边距
|
||||
navPaddingTop.value = (statusBarH + 10) + 'px'
|
||||
|
||||
const optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)
|
||||
const mid = optObj.getString('merchantId') ?? ''
|
||||
if (mid !== '') {
|
||||
merchantId.value = mid
|
||||
}
|
||||
const mname = optObj.getString('merchantName') ?? ''
|
||||
if (mname !== '') {
|
||||
headerTitle.value = mname
|
||||
}
|
||||
const optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)
|
||||
const mid = optObj.getString('merchantId') ?? ''
|
||||
if (mid !== '') {
|
||||
merchantId.value = mid
|
||||
}
|
||||
const mname = optObj.getString('merchantName') ?? ''
|
||||
if (mname !== '') {
|
||||
headerTitle.value = mname
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
supabaseService.ensureSession().then((uid) => {
|
||||
if (uid != null) {
|
||||
currentUserId.value = uid
|
||||
} else {
|
||||
getCurrentUser().then((user) => {
|
||||
if (user != null) {
|
||||
currentUserId.value = user.id ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
supabaseService.ensureSession().then((uid) => {
|
||||
if (uid != null) {
|
||||
currentUserId.value = uid
|
||||
} else {
|
||||
getCurrentUser().then((user) => {
|
||||
if (user != null) {
|
||||
currentUserId.value = user.id ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadMerchantInfo()
|
||||
loadChatHistory()
|
||||
setupRealtimeSubscription()
|
||||
})
|
||||
loadMerchantInfo()
|
||||
loadChatHistory()
|
||||
setupRealtimeSubscription()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -425,7 +425,7 @@ const sendMessage = async () => {
|
||||
if (merchantId.value != '') {
|
||||
console.log('[sendMessage] 开始发送消息到:', merchantId.value)
|
||||
const success = await supabaseService.sendMessage(merchantId.value, content)
|
||||
console.log('[sendMessage] 发送结果:', success)
|
||||
console.log('[sendMessage] 发送结果', success)
|
||||
if (!success) {
|
||||
uni.showToast({
|
||||
title: '发送失败',
|
||||
@@ -687,13 +687,23 @@ const goBack = () => {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 5px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 聊天输入区 */
|
||||
/* 聊天输入框 */
|
||||
.chat-input {
|
||||
background-color: white;
|
||||
border-top: 1px solid #eee;
|
||||
@@ -781,3 +791,4 @@ const goBack = () => {
|
||||
|
||||
/* 响应式适配 removed for strict uv-app-x compliance */
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user