fix(navBar): 修复导航栏白屏问题及删除无用客服页代码
- NavBar 组件中移除 useNavigate 钩子,改用 Taro.navigateBack 解决 Taro 4.1.11 不支持 useNavigate 导致白屏的问题 - 删除未注册且重复引用的客服页面源码及配置文件,避免热更新和编译冲突 - 全局搜索确认无其他 useNavigate 和 useReachBottom 引用 - 通过 TypeScript 检查确保修改文件无错误
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
export default {
|
||||
navigationBarTitleText: '在线客服',
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
import React, { useEffect, useState, useCallback, useRef } from 'react'
|
||||
import { View, Text, Button, Input, ScrollView } from '@tarojs/components'
|
||||
import Taro, { useReachBottom } from '@tarojs/taro'
|
||||
import NavBar from '@/components/NavBar'
|
||||
import LoadMore from '@/components/common/LoadMore'
|
||||
import EmptyState from '@/components/common/EmptyState'
|
||||
import { pageShopChatConversation, addShopChatConversation } from '@/api/shop/shopChatConversation'
|
||||
import { pageShopChatMessage, addShopChatMessage } from '@/api/shop/shopChatMessage'
|
||||
import type { ShopChatConversation, ShopChatConversationParam } from '@/api/shop/shopChatConversation/model'
|
||||
import type { ShopChatMessage } from '@/api/shop/shopChatMessage/model'
|
||||
import type { PageResult } from '@/api'
|
||||
|
||||
definePageConfig({
|
||||
navigationBarTitleText: '在线客服',
|
||||
})
|
||||
|
||||
const SERVICE_ONLINE_HOURS = '9:00-21:00'
|
||||
const SERVICE_HOTLINE = '400-888-8888'
|
||||
const SERVICE_WECHAT = 'shop_service'
|
||||
|
||||
const CustomerServicePage: React.FC = () => {
|
||||
const [conversations, setConversations] = useState<ShopChatConversation[]>([])
|
||||
const [currentConversation, setCurrentConversation] = useState<ShopChatConversation | null>(null)
|
||||
const [messages, setMessages] = useState<ShopChatMessage[]>([])
|
||||
const [inputMessage, setInputMessage] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [sending, setSending] = useState(false)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [msgPage, setMsgPage] = useState(1)
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
const scrollToBottom = useRef(false)
|
||||
|
||||
const isOnline = () => {
|
||||
const now = new Date()
|
||||
const h = now.getHours()
|
||||
return h >= 9 && h < 21
|
||||
}
|
||||
|
||||
// 加载会话列表
|
||||
const loadConversations = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const params: ShopChatConversationParam = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
order: 'desc',
|
||||
sort: 'updateTime',
|
||||
}
|
||||
const result: PageResult<ShopChatConversation> = await pageShopChatConversation(params)
|
||||
if (result?.list) {
|
||||
setConversations(result.list)
|
||||
if (result.list.length > 0) {
|
||||
const latest = result.list[0]
|
||||
setCurrentConversation(latest)
|
||||
loadMessages(latest.id!, 1, false)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取会话列表失败:', e)
|
||||
setConversations([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 加载消息列表
|
||||
const loadMessages = useCallback(async (conversationId: number, pageNum: number = 1, isLoadMore = false) => {
|
||||
if (isLoadMore) setLoadingMore(true)
|
||||
try {
|
||||
const result = await pageShopChatMessage({
|
||||
conversationId,
|
||||
page: pageNum,
|
||||
limit: 20,
|
||||
order: 'asc',
|
||||
sort: 'createTime',
|
||||
} as any)
|
||||
if (result?.list) {
|
||||
if (isLoadMore) {
|
||||
setMessages(prev => [...result.list!, ...prev])
|
||||
} else {
|
||||
setMessages(result.list)
|
||||
scrollToBottom.current = true
|
||||
}
|
||||
setHasMore((result.list.length ?? 0) >= 20)
|
||||
setMsgPage(pageNum)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取消息列表失败:', e)
|
||||
if (!isLoadMore) setMessages([])
|
||||
} finally {
|
||||
setLoadingMore(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
loadConversations()
|
||||
}, [loadConversations])
|
||||
|
||||
useReachBottom(() => {
|
||||
if (hasMore && currentConversation && !loadingMore) {
|
||||
loadMessages(currentConversation.id!, msgPage + 1, true)
|
||||
}
|
||||
})
|
||||
|
||||
// 创建新会话
|
||||
const ensureConversation = async (): Promise<ShopChatConversation | null> => {
|
||||
if (currentConversation) return currentConversation
|
||||
try {
|
||||
await addShopChatConversation({ type: 0 })
|
||||
// 重新拉取会话列表获取新会话
|
||||
const result = await pageShopChatConversation({ page: 1, limit: 20, order: 'desc', sort: 'updateTime' })
|
||||
if (result?.list && result.list.length > 0) {
|
||||
const conv = result.list[0]
|
||||
setConversations(result.list)
|
||||
setCurrentConversation(conv)
|
||||
return conv
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('创建会话失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
const handleSendMessage = async () => {
|
||||
const text = inputMessage.trim()
|
||||
if (!text) {
|
||||
Taro.showToast({ title: '请输入消息内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
setSending(true)
|
||||
setInputMessage('')
|
||||
try {
|
||||
const conv = await ensureConversation()
|
||||
if (!conv?.id) {
|
||||
Taro.showToast({ title: '无法创建会话,请稍后重试', icon: 'none' })
|
||||
setInputMessage(text)
|
||||
return
|
||||
}
|
||||
|
||||
await addShopChatMessage({
|
||||
content: text,
|
||||
type: 'text',
|
||||
toUserId: 0, // 发送给客服(服务端路由)
|
||||
} as any)
|
||||
|
||||
// 消息发送成功后刷新消息列表
|
||||
await loadMessages(conv.id, 1, false)
|
||||
|
||||
// 同步更新会话列表 lastMessage
|
||||
setConversations(prev => prev.map(c =>
|
||||
c.id === conv.id ? { ...c, lastMessage: text, updateTime: new Date().toISOString() } : c
|
||||
))
|
||||
} catch (e: any) {
|
||||
console.error('发送消息失败:', e)
|
||||
setInputMessage(text)
|
||||
Taro.showToast({ title: e?.message || '发送失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
setSending(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 拨打热线
|
||||
const handleCallHotline = () => {
|
||||
Taro.makePhoneCall({ phoneNumber: SERVICE_HOTLINE })
|
||||
}
|
||||
|
||||
// 复制微信号
|
||||
const handleCopyWechat = () => {
|
||||
Taro.setClipboardData({
|
||||
data: SERVICE_WECHAT,
|
||||
success: () => Taro.showToast({ title: '已复制微信号', icon: 'success' }),
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转帮助中心
|
||||
const handleGoToHelp = () => {
|
||||
Taro.navigateTo({ url: '/pages/user/help-center' })
|
||||
}
|
||||
|
||||
// 选择会话
|
||||
const handleSelectConversation = (conv: ShopChatConversation) => {
|
||||
setCurrentConversation(conv)
|
||||
setMessages([])
|
||||
setMsgPage(1)
|
||||
setHasMore(true)
|
||||
loadMessages(conv.id!, 1, false)
|
||||
}
|
||||
|
||||
const formatTime = (timeStr?: string) => {
|
||||
if (!timeStr) return ''
|
||||
const date = new Date(timeStr)
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(date.getDate()).padStart(2, '0')
|
||||
const h = String(date.getHours()).padStart(2, '0')
|
||||
const min = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${m}-${d} ${h}:${min}`
|
||||
}
|
||||
|
||||
const online = isOnline()
|
||||
|
||||
return (
|
||||
<View className="flex flex-col min-h-screen bg-gray-100">
|
||||
<NavBar title="在线客服" />
|
||||
|
||||
<ScrollView scrollY className="flex-1">
|
||||
{/* 客服状态栏 */}
|
||||
<View className="bg-white px-4 py-3 flex items-center justify-between">
|
||||
<View className="flex items-center gap-2">
|
||||
<View
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: online ? '#52c41a' : '#d1d5db' }}
|
||||
/>
|
||||
<Text className="text-sm text-gray-700">{online ? '客服在线' : '客服离线'}</Text>
|
||||
</View>
|
||||
<Text className="text-xs text-gray-400">服务时间: {SERVICE_ONLINE_HOURS}</Text>
|
||||
</View>
|
||||
|
||||
{/* 微信在线客服按钮 */}
|
||||
<View className="px-4 py-3 bg-white border-t border-gray-50">
|
||||
<Button
|
||||
openType="contact"
|
||||
className="w-full h-12 rounded-lg text-white font-medium text-base border-0"
|
||||
style={{ backgroundColor: '#07c160', lineHeight: '48px' }}
|
||||
>
|
||||
微信在线客服
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{/* 消息记录入口 */}
|
||||
{!showHistory && (
|
||||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg">
|
||||
<View
|
||||
className="flex items-center justify-between"
|
||||
onClick={() => setShowHistory(true)}
|
||||
>
|
||||
<View className="flex items-center gap-2">
|
||||
<Text className="text-lg">💬</Text>
|
||||
<Text className="text-sm text-gray-700">
|
||||
历史消息记录 {conversations.length > 0 ? `(${conversations.length}条会话)` : ''}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="text-gray-400 text-sm">→</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 历史会话与消息列表 */}
|
||||
{showHistory && (
|
||||
<View className="mx-4 mt-4">
|
||||
<View className="flex items-center justify-between mb-2">
|
||||
<Text className="text-sm font-medium text-gray-700">历史会话</Text>
|
||||
<Text
|
||||
className="text-sm"
|
||||
style={{ color: '#3b82f6' }}
|
||||
onClick={() => setShowHistory(false)}
|
||||
>
|
||||
收起
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{loading ? (
|
||||
<LoadMore loading />
|
||||
) : conversations.length === 0 ? (
|
||||
<EmptyState message="暂无历史会话" small />
|
||||
) : (
|
||||
<View>
|
||||
{conversations.map(conv => (
|
||||
<View
|
||||
key={conv.id}
|
||||
className="bg-white p-3 rounded-lg mb-2"
|
||||
style={{ border: currentConversation?.id === conv.id ? '2px solid #10b981' : 'none' }}
|
||||
onClick={() => handleSelectConversation(conv)}
|
||||
>
|
||||
<View className="flex justify-between items-center">
|
||||
<Text className="text-sm text-gray-700" style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{conv.content || conv.lastMessage || '暂无消息'}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-400 ml-2">{formatTime(conv.updateTime)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 当前会话的消息 */}
|
||||
{currentConversation && messages.length > 0 && (
|
||||
<View className="bg-white rounded-lg p-3 mb-4">
|
||||
<Text className="text-xs text-gray-400 mb-3 block">消息记录</Text>
|
||||
{loadingMore && <LoadMore loading />}
|
||||
{messages.map(msg => (
|
||||
<View
|
||||
key={msg.id}
|
||||
className={`flex mb-3 ${msg.formUserId === currentConversation.userId ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<View
|
||||
className="px-3 py-2 rounded-lg max-w-xs"
|
||||
style={{
|
||||
backgroundColor: msg.formUserId === currentConversation.userId ? '#10b981' : '#f3f4f6',
|
||||
maxWidth: '70%',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className="text-sm"
|
||||
style={{ color: msg.formUserId === currentConversation.userId ? '#fff' : '#374151' }}
|
||||
>
|
||||
{msg.content}
|
||||
</Text>
|
||||
<Text
|
||||
className="text-xs block mt-1"
|
||||
style={{ color: msg.formUserId === currentConversation.userId ? 'rgba(255,255,255,0.7)' : '#9ca3af' }}
|
||||
>
|
||||
{formatTime(msg.createTime)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 其他联系方式 */}
|
||||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg">
|
||||
<Text className="text-base font-medium text-gray-800 mb-3 block">其他联系方式</Text>
|
||||
<View
|
||||
className="flex items-center justify-between py-3 border-b border-gray-50"
|
||||
onClick={handleCallHotline}
|
||||
>
|
||||
<View className="flex items-center gap-2">
|
||||
<Text className="text-lg">📞</Text>
|
||||
<View>
|
||||
<Text className="text-sm text-gray-700 block">客服热线</Text>
|
||||
<Text className="text-xs text-gray-400">{SERVICE_HOTLINE}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text className="text-xs" style={{ color: '#3b82f6' }}>拨打</Text>
|
||||
</View>
|
||||
<View
|
||||
className="flex items-center justify-between py-3"
|
||||
onClick={handleCopyWechat}
|
||||
>
|
||||
<View className="flex items-center gap-2">
|
||||
<Text className="text-lg">💬</Text>
|
||||
<View>
|
||||
<Text className="text-sm text-gray-700 block">微信号</Text>
|
||||
<Text className="text-xs text-gray-400">{SERVICE_WECHAT}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text className="text-xs" style={{ color: '#3b82f6' }}>复制</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 帮助中心入口 */}
|
||||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg mb-4">
|
||||
<Text className="text-base font-medium text-gray-800 mb-3 block">常见问题</Text>
|
||||
<View
|
||||
className="flex items-center justify-between py-2"
|
||||
onClick={handleGoToHelp}
|
||||
>
|
||||
<Text className="text-sm text-gray-700">查看帮助中心</Text>
|
||||
<Text className="text-gray-400 text-sm">→</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 留言(离线时) */}
|
||||
{!online && (
|
||||
<View className="bg-white mx-4 mb-4 p-4 rounded-lg">
|
||||
<Text className="text-base font-medium text-gray-800 mb-3 block">离线留言</Text>
|
||||
<Text className="text-sm text-gray-400 block">
|
||||
客服暂时不在线,可在下方输入框留言,客服上线后会尽快回复您。
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{/* 消息输入区 - 始终显示,支持发送留言 */}
|
||||
<View className="bg-white border-t border-gray-200 px-4 py-3 flex items-center gap-2">
|
||||
<Input
|
||||
className="flex-1 border border-gray-200 rounded-full text-sm"
|
||||
style={{ paddingLeft: '16px', paddingRight: '16px', height: '40px', lineHeight: '40px' }}
|
||||
placeholder={online ? '输入消息...' : '留言给客服...'}
|
||||
value={inputMessage}
|
||||
onInput={(e: any) => setInputMessage(e.detail.value)}
|
||||
onConfirm={handleSendMessage}
|
||||
disabled={sending}
|
||||
/>
|
||||
<View
|
||||
className="rounded-full flex items-center justify-center"
|
||||
style={{
|
||||
width: '72px',
|
||||
height: '40px',
|
||||
backgroundColor: sending ? '#d1d5db' : '#10b981',
|
||||
}}
|
||||
onClick={sending ? undefined : handleSendMessage}
|
||||
>
|
||||
<Text className="text-sm text-white">{sending ? '发送中' : '发送'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomerServicePage
|
||||
Reference in New Issue
Block a user