fix(customer-service): 修复在线客服功能使其可用
- 在微信客服按钮补全必要属性 sessionFrom、onContact、showMessageCard 和 sendMessageTitle - 将按钮样式由 Tailwind 类改为内联样式,避免 border-0 干扰原生按钮渲染 - 添加真机预览测试提示,解决开发者工具无法使用问题 - `ShopChatMessage` 和相关接口新增 conversationId 字段支持消息会话关联 - 消息发送时补全 conversationId 和发送人 ID,保证消息关联准确 - 将会话列表中引用的不存在字段 lastMessage 替换为 content - 移除无用的 useReachBottom,改用 ScrollView 的 onScrollToLower 事件加载历史消息 - 删除未使用的 scrollToBottom ref - 增加条件守卫避免非历史展开时加载更多消息
This commit is contained in:
@@ -244,3 +244,34 @@ adapter `/list` 接口忽略分页,一次返回全部收藏;前端 `listShop
|
|||||||
1. 从 `useCartContext` 额外解构 `totalCount`、`refresh`
|
1. 从 `useCartContext` 额外解构 `totalCount`、`refresh`
|
||||||
2. 引入 `useDidShow`,页面显示时(已登录)调 `refresh()` 拉取最新购物车,保证角标准确
|
2. 引入 `useDidShow`,页面显示时(已登录)调 `refresh()` 拉取最新购物车,保证角标准确
|
||||||
3. 购物车图标外包一层 `relative` 容器,`totalCount > 0` 时右上角显示红色圆角角标(>99 显示 `99+`),样式与商城列表页浮动购物车角标一致
|
3. 购物车图标外包一层 `relative` 容器,`totalCount > 0` 时右上角显示红色圆角角标(>99 显示 `99+`),样式与商城列表页浮动购物车角标一致
|
||||||
|
|
||||||
|
## 在线客服功能修复(2026-07-14)
|
||||||
|
|
||||||
|
### 问题
|
||||||
|
`pages/user/customer-service/index` 在线客服功能使用不了,用户已在微信后台添加客服人员。
|
||||||
|
|
||||||
|
### 根因(多个问题叠加)
|
||||||
|
1. **`openType="contact"` 按钮缺少关键属性**:缺少 `sessionFrom`(会话来源标识)、`onContact`(客服回调)、`showMessageCard`/`sendMessageTitle`(客服消息卡片),且按钮被 Tailwind `border-0` 类可能干扰原生渲染
|
||||||
|
2. **开发者工具限制**:`open-type="contact"` 在微信开发者工具中不生效,只能在真机上使用,页面缺少提示
|
||||||
|
3. **自定义聊天系统消息发送缺失 `conversationId`**:`addShopChatMessage` 调用未传 `conversationId`,消息无法关联到会话;`ShopChatMessage` 模型本身也缺少该字段
|
||||||
|
4. **消息发送缺失 `formUserId`**:发送人 ID 未设置
|
||||||
|
5. **`conv.lastMessage` 字段不存在**:`ShopChatConversation` 模型只有 `content` 字段,代码引用了不存在的 `lastMessage`
|
||||||
|
6. **`useReachBottom` 用错场景**:页面使用 `ScrollView` 组件,`useReachBottom` 是页面级滚动钩子,对 ScrollView 无效,应改用 `onScrollToLower`
|
||||||
|
7. **`scrollToBottom` ref 设了但从未使用**:死代码
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
1. **`src/api/shop/shopChatMessage/model/index.ts`**:
|
||||||
|
- `ShopChatMessage` 接口新增 `conversationId?: number`
|
||||||
|
- `ShopChatMessageParam` 接口新增 `conversationId?: number`
|
||||||
|
2. **`src/pages/user/customer-service/index.tsx`**(全面修复):
|
||||||
|
- 微信客服按钮补全 `sessionFrom="customer_service"`、`onContact`、`showMessageCard`、`sendMessageTitle`
|
||||||
|
- 按钮样式从 Tailwind 类改为 inline style(避免 `border-0` 干扰原生按钮),显式设置 `border: 'none'`
|
||||||
|
- 添加"需真机预览测试"提示文字
|
||||||
|
- `addShopChatMessage` 调用补全 `conversationId` 和 `formUserId`(从 `Taro.getStorageSync('UserId')` 获取)
|
||||||
|
- `conv.lastMessage` 全部改为 `conv.content`
|
||||||
|
- 会话列表更新 `lastMessage: text` 改为 `content: text`
|
||||||
|
- 移除 `useReachBottom`,改用 ScrollView `onScrollToLower` + `lowerThreshold={50}`
|
||||||
|
- 移除未使用的 `scrollToBottom` ref
|
||||||
|
- `pageShopChatMessage` 参数去掉 `as any` 断言(模型已支持 `conversationId`)
|
||||||
|
- 新增 `getCurrentUserId()` 辅助函数
|
||||||
|
- `handleScrollToLower` 增加 `showHistory` 条件守卫(仅展开历史时才加载更多)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type { PageParam } from '@/api';
|
|||||||
export interface ShopChatMessage {
|
export interface ShopChatMessage {
|
||||||
// 自增ID
|
// 自增ID
|
||||||
id?: number;
|
id?: number;
|
||||||
|
// 会话ID
|
||||||
|
conversationId?: number;
|
||||||
// 发送人ID
|
// 发送人ID
|
||||||
formUserId?: number;
|
formUserId?: number;
|
||||||
// 发送人名称
|
// 发送人名称
|
||||||
@@ -60,4 +62,5 @@ export interface ShopChatMessage {
|
|||||||
export interface ShopChatMessageParam extends PageParam {
|
export interface ShopChatMessageParam extends PageParam {
|
||||||
id?: number;
|
id?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
|
conversationId?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect, useState, useCallback, useRef } from 'react'
|
import React, { useEffect, useState, useCallback, useRef } from 'react'
|
||||||
import { View, Text, Button, Input, ScrollView } from '@tarojs/components'
|
import { View, Text, Button, Input, ScrollView } from '@tarojs/components'
|
||||||
import Taro, { useReachBottom } from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import NavBar from '@/components/NavBar'
|
import NavBar from '@/components/NavBar'
|
||||||
import LoadMore from '@/components/common/LoadMore'
|
import LoadMore from '@/components/common/LoadMore'
|
||||||
import EmptyState from '@/components/common/EmptyState'
|
import EmptyState from '@/components/common/EmptyState'
|
||||||
import { pageShopChatConversation, addShopChatConversation } from '@/api/shop/shopChatConversation'
|
import { pageShopChatConversation, addShopChatConversation } from '@/api/shop/shopChatConversation'
|
||||||
import { pageShopChatMessage, addShopChatMessage } from '@/api/shop/shopChatMessage'
|
import { pageShopChatMessage, addShopChatMessage } from '@/api/shop/shopChatMessage'
|
||||||
import type { ShopChatConversation, ShopChatConversationParam } from '@/api/shop/shopChatConversation/model'
|
import type { ShopChatConversation, ShopChatConversationParam } from '@/api/shop/shopChatConversation/model'
|
||||||
import type { ShopChatMessage } from '@/api/shop/shopChatMessage/model'
|
import type { ShopChatMessage, ShopChatMessageParam } from '@/api/shop/shopChatMessage/model'
|
||||||
import type { PageResult } from '@/api'
|
import type { PageResult } from '@/api'
|
||||||
|
|
||||||
definePageConfig({
|
definePageConfig({
|
||||||
@@ -29,7 +29,6 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const [msgPage, setMsgPage] = useState(1)
|
const [msgPage, setMsgPage] = useState(1)
|
||||||
const [showHistory, setShowHistory] = useState(false)
|
const [showHistory, setShowHistory] = useState(false)
|
||||||
const scrollToBottom = useRef(false)
|
|
||||||
|
|
||||||
const isOnline = () => {
|
const isOnline = () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@@ -37,6 +36,12 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
return h >= 9 && h < 21
|
return h >= 9 && h < 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前用户ID
|
||||||
|
const getCurrentUserId = (): number | undefined => {
|
||||||
|
const id = Taro.getStorageSync('UserId')
|
||||||
|
return id || undefined
|
||||||
|
}
|
||||||
|
|
||||||
// 加载会话列表
|
// 加载会话列表
|
||||||
const loadConversations = useCallback(async () => {
|
const loadConversations = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -68,19 +73,19 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
const loadMessages = useCallback(async (conversationId: number, pageNum: number = 1, isLoadMore = false) => {
|
const loadMessages = useCallback(async (conversationId: number, pageNum: number = 1, isLoadMore = false) => {
|
||||||
if (isLoadMore) setLoadingMore(true)
|
if (isLoadMore) setLoadingMore(true)
|
||||||
try {
|
try {
|
||||||
const result = await pageShopChatMessage({
|
const params: ShopChatMessageParam = {
|
||||||
conversationId,
|
conversationId,
|
||||||
page: pageNum,
|
page: pageNum,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
sort: 'createTime',
|
sort: 'createTime',
|
||||||
} as any)
|
}
|
||||||
|
const result = await pageShopChatMessage(params)
|
||||||
if (result?.list) {
|
if (result?.list) {
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
setMessages(prev => [...result.list!, ...prev])
|
setMessages(prev => [...result.list!, ...prev])
|
||||||
} else {
|
} else {
|
||||||
setMessages(result.list)
|
setMessages(result.list)
|
||||||
scrollToBottom.current = true
|
|
||||||
}
|
}
|
||||||
setHasMore((result.list.length ?? 0) >= 20)
|
setHasMore((result.list.length ?? 0) >= 20)
|
||||||
setMsgPage(pageNum)
|
setMsgPage(pageNum)
|
||||||
@@ -97,11 +102,12 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
loadConversations()
|
loadConversations()
|
||||||
}, [loadConversations])
|
}, [loadConversations])
|
||||||
|
|
||||||
useReachBottom(() => {
|
// ScrollView 滚动到底部加载更多历史消息
|
||||||
if (hasMore && currentConversation && !loadingMore) {
|
const handleScrollToLower = () => {
|
||||||
|
if (hasMore && currentConversation && !loadingMore && showHistory) {
|
||||||
loadMessages(currentConversation.id!, msgPage + 1, true)
|
loadMessages(currentConversation.id!, msgPage + 1, true)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// 创建新会话
|
// 创建新会话
|
||||||
const ensureConversation = async (): Promise<ShopChatConversation | null> => {
|
const ensureConversation = async (): Promise<ShopChatConversation | null> => {
|
||||||
@@ -140,18 +146,21 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = getCurrentUserId()
|
||||||
await addShopChatMessage({
|
await addShopChatMessage({
|
||||||
|
conversationId: conv.id,
|
||||||
content: text,
|
content: text,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
formUserId: userId,
|
||||||
toUserId: 0, // 发送给客服(服务端路由)
|
toUserId: 0, // 发送给客服(服务端路由)
|
||||||
} as any)
|
})
|
||||||
|
|
||||||
// 消息发送成功后刷新消息列表
|
// 消息发送成功后刷新消息列表
|
||||||
await loadMessages(conv.id, 1, false)
|
await loadMessages(conv.id, 1, false)
|
||||||
|
|
||||||
// 同步更新会话列表 lastMessage
|
// 同步更新会话列表 content 字段
|
||||||
setConversations(prev => prev.map(c =>
|
setConversations(prev => prev.map(c =>
|
||||||
c.id === conv.id ? { ...c, lastMessage: text, updateTime: new Date().toISOString() } : c
|
c.id === conv.id ? { ...c, content: text, updateTime: new Date().toISOString() } : c
|
||||||
))
|
))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('发送消息失败:', e)
|
console.error('发送消息失败:', e)
|
||||||
@@ -162,6 +171,11 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 微信客服回调 - 用户从客服会话返回时触发
|
||||||
|
const handleContact = (e: any) => {
|
||||||
|
console.log('客服会话回调:', e.detail)
|
||||||
|
}
|
||||||
|
|
||||||
// 拨打热线
|
// 拨打热线
|
||||||
const handleCallHotline = () => {
|
const handleCallHotline = () => {
|
||||||
Taro.makePhoneCall({ phoneNumber: SERVICE_HOTLINE })
|
Taro.makePhoneCall({ phoneNumber: SERVICE_HOTLINE })
|
||||||
@@ -205,7 +219,12 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
<View className="flex flex-col min-h-screen bg-gray-100">
|
<View className="flex flex-col min-h-screen bg-gray-100">
|
||||||
<NavBar title="在线客服" />
|
<NavBar title="在线客服" />
|
||||||
|
|
||||||
<ScrollView scrollY className="flex-1">
|
<ScrollView
|
||||||
|
scrollY
|
||||||
|
className="flex-1"
|
||||||
|
onScrollToLower={handleScrollToLower}
|
||||||
|
lowerThreshold={50}
|
||||||
|
>
|
||||||
{/* 客服状态栏 */}
|
{/* 客服状态栏 */}
|
||||||
<View className="bg-white px-4 py-3 flex items-center justify-between">
|
<View className="bg-white px-4 py-3 flex items-center justify-between">
|
||||||
<View className="flex items-center gap-2">
|
<View className="flex items-center gap-2">
|
||||||
@@ -222,11 +241,25 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
<View className="px-4 py-3 bg-white border-t border-gray-50">
|
<View className="px-4 py-3 bg-white border-t border-gray-50">
|
||||||
<Button
|
<Button
|
||||||
openType="contact"
|
openType="contact"
|
||||||
className="w-full h-12 rounded-lg text-white font-medium text-base border-0"
|
sessionFrom="customer_service"
|
||||||
style={{ backgroundColor: '#07c160', lineHeight: '48px' }}
|
onContact={handleContact}
|
||||||
|
showMessageCard
|
||||||
|
sendMessageTitle="欢迎咨询"
|
||||||
|
className="w-full rounded-lg text-white font-medium text-base"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#07c160',
|
||||||
|
lineHeight: '48px',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: 'none',
|
||||||
|
padding: '0',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
微信在线客服
|
微信在线客服
|
||||||
</Button>
|
</Button>
|
||||||
|
<Text className="text-xs text-gray-400 block text-center mt-2">
|
||||||
|
点击进入微信客服对话(开发者工具中不可用,请用真机预览测试)
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 消息记录入口 */}
|
{/* 消息记录入口 */}
|
||||||
@@ -276,7 +309,7 @@ const CustomerServicePage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<View className="flex justify-between items-center">
|
<View className="flex justify-between items-center">
|
||||||
<Text className="text-sm text-gray-700" style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
<Text className="text-sm text-gray-700" style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
{conv.content || conv.lastMessage || '暂无消息'}
|
{conv.content || '暂无消息'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-xs text-gray-400 ml-2">{formatTime(conv.updateTime)}</Text>
|
<Text className="text-xs text-gray-400 ml-2">{formatTime(conv.updateTime)}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user