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([]) const [currentConversation, setCurrentConversation] = useState(null) const [messages, setMessages] = useState([]) 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 = 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 => { 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 ( {/* 客服状态栏 */} {online ? '客服在线' : '客服离线'} 服务时间: {SERVICE_ONLINE_HOURS} {/* 微信在线客服按钮 */} {/* 消息记录入口 */} {!showHistory && ( setShowHistory(true)} > 💬 历史消息记录 {conversations.length > 0 ? `(${conversations.length}条会话)` : ''} )} {/* 历史会话与消息列表 */} {showHistory && ( 历史会话 setShowHistory(false)} > 收起 {loading ? ( ) : conversations.length === 0 ? ( ) : ( {conversations.map(conv => ( handleSelectConversation(conv)} > {conv.content || conv.lastMessage || '暂无消息'} {formatTime(conv.updateTime)} ))} )} {/* 当前会话的消息 */} {currentConversation && messages.length > 0 && ( 消息记录 {loadingMore && } {messages.map(msg => ( {msg.content} {formatTime(msg.createTime)} ))} )} )} {/* 其他联系方式 */} 其他联系方式 📞 客服热线 {SERVICE_HOTLINE} 拨打 💬 微信号 {SERVICE_WECHAT} 复制 {/* 帮助中心入口 */} 常见问题 查看帮助中心 {/* 留言(离线时) */} {!online && ( 离线留言 客服暂时不在线,可在下方输入框留言,客服上线后会尽快回复您。 )} {/* 消息输入区 - 始终显示,支持发送留言 */} setInputMessage(e.detail.value)} onConfirm={handleSendMessage} disabled={sending} /> {sending ? '发送中' : '发送'} ) } export default CustomerServicePage