285 lines
8.7 KiB
TypeScript
285 lines
8.7 KiB
TypeScript
import {useState, useEffect, useRef} from 'react'
|
|
import {View, Text} from '@tarojs/components'
|
|
import Taro, {useDidShow, useRouter, useLoad} from '@tarojs/taro'
|
|
import {Input, Button} from '@nutui/nutui-react-taro'
|
|
import {Voice, FaceMild} from '@nutui/icons-react-taro'
|
|
import {getClinicDoctorUserByUserId} from "@/api/clinic/clinicDoctorUser";
|
|
import {addShopChatMessage, listShopChatMessage} from "@/api/shop/shopChatMessage";
|
|
import {ShopChatMessage} from "@/api/shop/shopChatMessage/model";
|
|
import {addShopChatConversation, chatConversationByBothUserId} from "@/api/shop/shopChatConversation";
|
|
// @ts-ignore
|
|
import {WS_URL} from "@/config/env";
|
|
import {clinicPatientUserByPatientUserId} from "@/api/clinic/clinicPatientUser";
|
|
|
|
const CustomerIndex = () => {
|
|
const {params} = useRouter()
|
|
const [messages, setMessages] = useState<ShopChatMessage[]>([])
|
|
const [conversationId, setConversationId] = useState<number | null>(null)
|
|
const [friendUserId, setFriendUserId] = useState<string>('')
|
|
const [messageInput, setMessageInput] = useState<string>('')
|
|
const [sending, setSending] = useState<boolean>(false)
|
|
const [isDoctor, setIsDoctor] = useState<boolean>(false)
|
|
const socketRef = useRef<Taro.SocketTask | null>(null)
|
|
|
|
const quickActions = [
|
|
{label: '开方', type: 'prescription'},
|
|
{label: '快捷回复', type: 'quickReply'},
|
|
{label: '发问诊单', type: 'sendInquiry'}
|
|
]
|
|
|
|
const handleQuickAction = (actionType: string) => {
|
|
switch (actionType) {
|
|
case 'prescription':
|
|
if (friendUserId) {
|
|
Taro.navigateTo({url: `/doctor/orders/add?id=${friendUserId}`})
|
|
} else {
|
|
Taro.showToast({title: '请选择患者', icon: 'none'})
|
|
}
|
|
break
|
|
case 'quickReply':
|
|
Taro.showToast({title: '快捷回复敬请期待', icon: 'none'})
|
|
break
|
|
case 'sendInquiry':
|
|
Taro.showToast({title: '问诊单功能敬请期待', icon: 'none'})
|
|
break
|
|
}
|
|
}
|
|
|
|
const fetchData = async (userId?: string) => {
|
|
if (!userId) return
|
|
try {
|
|
console.log("Taro.getStorageSync('Doctor')", Taro.getStorageSync('Doctor'))
|
|
if (Taro.getStorageSync('Doctor')) {
|
|
setIsDoctor(true)
|
|
}
|
|
const isDoctorData = Taro.getStorageSync('Doctor')
|
|
if (!isDoctorData) {
|
|
const doctorUser = await getClinicDoctorUserByUserId(Number(params.userId))
|
|
if (doctorUser?.realName) {
|
|
await Taro.setNavigationBarTitle({title: `${doctorUser.realName}`})
|
|
}
|
|
} else {
|
|
const patient = await clinicPatientUserByPatientUserId(Number(params.userId))
|
|
if (patient?.realName) {
|
|
await Taro.setNavigationBarTitle({title: `${patient.realName}`})
|
|
}
|
|
}
|
|
|
|
let conversation = await chatConversationByBothUserId(userId)
|
|
if (!conversation) {
|
|
conversation = await addShopChatConversation({
|
|
friendId: parseInt(userId, 10),
|
|
content: ''
|
|
})
|
|
}
|
|
|
|
if (conversation?.id) {
|
|
setConversationId(conversation.id)
|
|
const messageList = await listShopChatMessage({conversationId: conversation.id})
|
|
setMessages(messageList || [])
|
|
} else {
|
|
setMessages([])
|
|
}
|
|
} catch (error) {
|
|
console.error('加载聊天数据失败:', error)
|
|
Taro.showToast({title: '聊天加载失败', icon: 'none'})
|
|
}
|
|
}
|
|
|
|
const connectWebSocket = async (id: number) => {
|
|
const base = (WS_URL || '').replace(/\/$/, '')
|
|
if (!base) {
|
|
console.warn('WS_URL 未配置')
|
|
return
|
|
}
|
|
|
|
if (socketRef.current) {
|
|
socketRef.current.close({})
|
|
}
|
|
|
|
const userId = Taro.getStorageSync('UserId')
|
|
const result = Taro.connectSocket({
|
|
url: `${base}/${id}_${userId}`
|
|
})
|
|
|
|
const socketTask = typeof (result as Promise<any>)?.then === 'function'
|
|
? await (result as Promise<Taro.SocketTask>)
|
|
: (result as Taro.SocketTask)
|
|
|
|
if (!socketTask) {
|
|
return
|
|
}
|
|
|
|
socketRef.current = socketTask
|
|
|
|
socketTask.onOpen(() => {
|
|
console.log('WebSocket连接成功')
|
|
})
|
|
|
|
socketTask.onMessage((event) => {
|
|
console.log('收到消息:', event,)
|
|
try {
|
|
if (event.data === '连接成功' || event.data === 'pong') return
|
|
const payload = typeof event.data === 'string' ? JSON.parse(event.data) : event.data
|
|
if (!payload) return
|
|
if (Array.isArray(payload)) {
|
|
setMessages(prev => [...prev, ...payload])
|
|
} else {
|
|
payload.isMine = parseInt(payload.fromUserId) !== parseInt(params?.userId)
|
|
setMessages(prev => [...prev, payload])
|
|
}
|
|
} catch (err) {
|
|
console.error('解析消息失败:', err)
|
|
}
|
|
})
|
|
|
|
socketTask.onError((err) => {
|
|
console.error('WebSocket异常:', err)
|
|
})
|
|
|
|
socketTask.onClose(() => {
|
|
socketRef.current = null
|
|
})
|
|
}
|
|
|
|
const handleSendMessage = async () => {
|
|
const content = messageInput.trim()
|
|
if (!content) {
|
|
Taro.showToast({title: '请输入内容', icon: 'none'})
|
|
return
|
|
}
|
|
if (!conversationId || !friendUserId) {
|
|
Taro.showToast({title: '会话未初始化', icon: 'none'})
|
|
return
|
|
}
|
|
if (sending) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
setSending(true)
|
|
await addShopChatMessage({
|
|
content,
|
|
conversationId,
|
|
toUserId: parseInt(friendUserId, 10),
|
|
type: 'text'
|
|
})
|
|
|
|
// const localMessage: ShopChatMessage = {
|
|
// id: Date.now(),
|
|
// content,
|
|
// conversationId,
|
|
// toUserId: parseInt(friendUserId, 10),
|
|
// type: 'text',
|
|
// isMine: true
|
|
// }
|
|
//
|
|
// setMessages(prev => [...prev, localMessage])
|
|
setMessageInput('')
|
|
} catch (error) {
|
|
console.error('发送消息失败:', error)
|
|
Taro.showToast({title: '发送失败,请重试', icon: 'none'})
|
|
} finally {
|
|
setSending(false)
|
|
}
|
|
}
|
|
|
|
useLoad((options) => {
|
|
if (options?.userId) {
|
|
const userId = String(options.userId)
|
|
setFriendUserId(userId)
|
|
fetchData(userId)
|
|
}
|
|
console.log('Taro.getStorageSync(\'UserId\')', Taro.getStorageSync('UserId'))
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (conversationId) {
|
|
connectWebSocket(conversationId).catch(err => {
|
|
console.error('WebSocket连接失败:', err)
|
|
})
|
|
}
|
|
return () => {
|
|
socketRef.current?.close()
|
|
socketRef.current = null
|
|
}
|
|
}, [conversationId])
|
|
|
|
return (
|
|
<View className="min-h-screen bg-gray-50 w-full pb-24">
|
|
<View className="px-4 pt-4 pb-24">
|
|
{messages.length === 0 ? (
|
|
<View className="mt-10 text-center text-gray-400 text-sm">
|
|
<Text>暂时还没有聊天记录</Text>
|
|
</View>
|
|
) : (
|
|
messages.map((item, index) => (
|
|
<View
|
|
key={item.id || `msg-${index}`}
|
|
className={`flex mb-4 ${item.isMine ? 'justify-end' : 'justify-start'}`}
|
|
>
|
|
<View
|
|
className={`px-3 py-2 rounded-2xl text-sm leading-relaxed break-words ${item.isMine ? 'bg-amber-400 text-white' : 'bg-white text-gray-800'}`}
|
|
style={{maxWidth: '75%'}}
|
|
>
|
|
<Text>{item.content}</Text>
|
|
</View>
|
|
</View>
|
|
))
|
|
)}
|
|
</View>
|
|
|
|
<View className="fixed bottom-0 left-0 right-0 bg-orange-50 pt-2 pb-8 px-3 border-t border-orange-100">
|
|
<View className="flex gap-3 mb-2 overflow-x-auto">
|
|
{quickActions.map(action => (
|
|
<View
|
|
key={action.type}
|
|
onClick={() => handleQuickAction(action.type)}
|
|
style={{
|
|
padding: '6px 12px',
|
|
borderRadius: '20px',
|
|
backgroundColor: '#fff',
|
|
fontSize: '14px',
|
|
color: '#8b5a2b',
|
|
boxShadow: '0 4px 10px rgba(0,0,0,0.05)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
flexShrink: 0
|
|
}}
|
|
>
|
|
<Text>{action.label}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
<View className="flex flex-1 items-center justify-between">
|
|
<Voice className="mx-2"/>
|
|
<Input
|
|
className="w-full"
|
|
style={{
|
|
borderRadius: '6px',
|
|
paddingLeft: '12px',
|
|
paddingRight: '12px'
|
|
}}
|
|
value={messageInput}
|
|
onChange={(value) => setMessageInput(value)}
|
|
confirmType="send"
|
|
onConfirm={handleSendMessage}
|
|
/>
|
|
<FaceMild size={26} className="ml-2"/>
|
|
<Button
|
|
size="small"
|
|
type="primary"
|
|
className="ml-2"
|
|
loading={sending}
|
|
onClick={handleSendMessage}
|
|
>
|
|
发送
|
|
</Button>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export default CustomerIndex;
|