211 lines
8.6 KiB
TypeScript
211 lines
8.6 KiB
TypeScript
import { getParticipantArtwork } from '@/utils/artwork'
|
|
import { formatConversationPreview } from '@/utils/chat-preview'
|
|
import PageTitle from '@/components/page-title'
|
|
import { getPageStyle } from '@/utils/page-style'
|
|
import { setActiveTabRoute } from '@/utils/tab-bar'
|
|
import { View, Text, ScrollView, Image } from '@tarojs/components'
|
|
import { useState } from 'react'
|
|
import Taro, { useDidShow } from '@tarojs/taro'
|
|
import { getToken } from '@/services/auth'
|
|
import { getConversations, ChatConversation } from '../../services/chat'
|
|
import { useSystemBanner } from '@/components/system-banner/use-system-banner'
|
|
import { usePageShare } from '@/utils/page-share'
|
|
import './index.scss'
|
|
|
|
export default function Chat() {
|
|
usePageShare()
|
|
const systemBanner = useSystemBanner()
|
|
const [conversations, setConversations] = useState<ChatConversation[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [isGuest, setIsGuest] = useState(false)
|
|
|
|
useDidShow(() => {
|
|
setActiveTabRoute('pages/chat/index')
|
|
void loadConversations()
|
|
})
|
|
|
|
const loadConversations = async () => {
|
|
if (!getToken()) {
|
|
setConversations([])
|
|
setIsGuest(true)
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
setIsGuest(false)
|
|
setLoading(true)
|
|
try {
|
|
const data = await getConversations()
|
|
setConversations(data)
|
|
} catch (error: any) {
|
|
console.error('[chat] load failed:', error)
|
|
Taro.showToast({
|
|
title: error?.message || '加载失败,请稍后重试',
|
|
icon: 'none',
|
|
})
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleConversationClick = (conversation: ChatConversation) => {
|
|
Taro.navigateTo({
|
|
url:
|
|
`/pages/chat/detail/index?id=${conversation.id}` +
|
|
`&source=${conversation.source}` +
|
|
`&name=${encodeURIComponent(conversation.name)}` +
|
|
`&peerKind=${conversation.peerKind}` +
|
|
`&roleKey=${encodeURIComponent(conversation.roleKey || '')}` +
|
|
`&conversationTypeName=${encodeURIComponent(conversation.conversationTypeName || '')}` +
|
|
`&peerId=${encodeURIComponent(conversation.peerId || '')}` +
|
|
`&childId=${conversation.childId || ''}` +
|
|
`&childName=${encodeURIComponent(conversation.childName || '')}` +
|
|
`&parentUserId=${conversation.parentUserId || ''}` +
|
|
`&deviceId=${encodeURIComponent(conversation.deviceId || '')}` +
|
|
`&channelLabel=${encodeURIComponent(conversation.channelLabel || '')}` +
|
|
`&canSend=${conversation.canSend ? '1' : '0'}`,
|
|
})
|
|
}
|
|
|
|
const handleGuestLogin = () => {
|
|
Taro.setStorageSync('postLoginRedirect', '/pages/chat/index')
|
|
Taro.navigateTo({ url: '/pages/login/index' })
|
|
}
|
|
|
|
const handleGuestPreviewTap = (title: string, content: string) => {
|
|
Taro.showModal({
|
|
title,
|
|
content,
|
|
showCancel: false,
|
|
confirmText: '我知道了',
|
|
})
|
|
}
|
|
|
|
return (
|
|
<View className='chat-page' style={getPageStyle(true)}>
|
|
<View className='page-header'>
|
|
<PageTitle title='玩伴' />
|
|
</View>
|
|
|
|
{isGuest ? (
|
|
<View className='guest-chat-shell'>
|
|
<View className='guest-card'>
|
|
<Text className='guest-card-icon'>💬</Text>
|
|
<Text className='guest-card-title'>亲子互动会话</Text>
|
|
<Text className='guest-card-desc'>这里会汇总宝贝、家长和 AI 玩伴之间的消息。绑定设备后,真实会话会按宝贝和设备自动归档。</Text>
|
|
</View>
|
|
|
|
<View className='guest-demo-card'>
|
|
<View className='guest-demo-header'>
|
|
<Text className='guest-demo-title'>会话示例</Text>
|
|
<View
|
|
className='guest-demo-tag'
|
|
onClick={() => handleGuestPreviewTap(
|
|
'会话预览',
|
|
'这里展示玩伴页的会话能力。绑定设备后,会显示宝贝、家长和 AI 玩伴的真实消息记录。'
|
|
)}
|
|
>
|
|
<Text className='guest-demo-tag-text'>预览</Text>
|
|
</View>
|
|
</View>
|
|
<View
|
|
className='guest-demo-item'
|
|
onClick={() => handleGuestPreviewTap(
|
|
'AI 玩伴',
|
|
'绑定设备后,可以在这里查看宝贝与 AI 玩伴的真实会话记录,包括聊天内容、时间和对应宝贝。'
|
|
)}
|
|
>
|
|
<View className='guest-demo-avatar'>
|
|
<Image className='conversation-avatar-art' src={getParticipantArtwork('ai')} mode='aspectFit' />
|
|
</View>
|
|
<View className='guest-demo-content'>
|
|
<Text className='guest-demo-name'>AI 玩伴</Text>
|
|
<Text className='guest-demo-message'>陪宝贝聊天、讲故事,也能留下可回看记录。</Text>
|
|
</View>
|
|
<View
|
|
className='guest-demo-action'
|
|
onClick={() => handleGuestPreviewTap(
|
|
'AI 玩伴',
|
|
'绑定设备后,可以在这里查看宝贝与 AI 玩伴的真实会话记录,包括聊天内容、时间和对应宝贝。'
|
|
)}
|
|
>
|
|
<Text className='guest-demo-action-text'>查看</Text>
|
|
</View>
|
|
</View>
|
|
<View
|
|
className='guest-demo-item'
|
|
onClick={() => handleGuestPreviewTap(
|
|
'家长留言',
|
|
'绑定设备后,家长可以查看消息记录,也可以给宝贝发送语音留言,设备端会收到对应提醒。'
|
|
)}
|
|
>
|
|
<View className='guest-demo-avatar parent'>
|
|
<Image className='conversation-avatar-art' src={getParticipantArtwork('parent')} mode='aspectFit' />
|
|
</View>
|
|
<View className='guest-demo-content'>
|
|
<Text className='guest-demo-name'>家长留言</Text>
|
|
<Text className='guest-demo-message'>家长可查看设备消息,并给宝贝发送语音留言。</Text>
|
|
</View>
|
|
<View
|
|
className='guest-demo-action'
|
|
onClick={() => handleGuestPreviewTap(
|
|
'家长留言',
|
|
'绑定设备后,家长可以查看消息记录,也可以给宝贝发送语音留言,设备端会收到对应提醒。'
|
|
)}
|
|
>
|
|
<Text className='guest-demo-action-text'>查看</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
<View className='guest-guide-card'>
|
|
<Text className='guest-guide-title'>开始使用</Text>
|
|
<Text className='guest-guide-desc'>浏览功能不需要登录;当你需要绑定设备、查看真实会话记录时,再进入登录和绑定流程。</Text>
|
|
<View className='guest-card-btn secondary' onClick={handleGuestLogin}>
|
|
<Text className='guest-card-btn-text'>去登录 / 绑定设备</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
) : loading ? (
|
|
<View className='loading'>
|
|
<Text>加载中...</Text>
|
|
</View>
|
|
) : conversations.length === 0 ? (
|
|
<View className='empty-card'>
|
|
<Text className='empty-title'>当前宝贝暂无会话</Text>
|
|
</View>
|
|
) : (
|
|
<ScrollView className='conversation-list' scrollY enhanced showScrollbar={false}>
|
|
{conversations.map((conversation) => (
|
|
<View
|
|
key={conversation.key}
|
|
className='conversation-item'
|
|
onClick={() => handleConversationClick(conversation)}
|
|
>
|
|
<View className='conversation-avatar'>
|
|
<Image className='conversation-avatar-art' src={getParticipantArtwork(conversation.peerKind, conversation.roleKey)} mode='aspectFit' />
|
|
</View>
|
|
<View className='conversation-content'>
|
|
<View className='conversation-header'>
|
|
<View className='conversation-name-wrap'>
|
|
<Text className='conversation-name'>{conversation.name}</Text>
|
|
<Text className={`conversation-tag ${conversation.peerKind}`}>{conversation.typeLabel}</Text>
|
|
</View>
|
|
<Text className='conversation-time'>{conversation.time}</Text>
|
|
</View>
|
|
<View className='conversation-footer'>
|
|
<View className='last-message'>
|
|
{formatConversationPreview(conversation.lastMessage)}
|
|
</View>
|
|
</View>
|
|
{conversation.childName && <Text className='conversation-meta'>{conversation.childName}</Text>}
|
|
</View>
|
|
</View>
|
|
))}
|
|
</ScrollView>
|
|
)}
|
|
{systemBanner}
|
|
</View>
|
|
)
|
|
}
|