Files
template-10559/src/chat/doctor/index.tsx
2025-12-01 10:14:44 +08:00

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;