# 06-AI对话页面 ## 目标 实现 APP 端 AI 健康问诊对话功能。 --- ## UI 设计参考 > 参考设计稿:`files/ui/问答页.png`、`files/ui/问答对话.png` ### 对话首页 | 区域 | 设计要点 | |------|----------| | 导航栏 | "AI健康助手" + 绿色 "在线" 状态 | | AI 欢迎语 | 机器人图标(蓝色 `#3B82F6`)+ 灰色气泡 | | 常见问题 | 灰色标签 + 白色圆角按钮(多个快捷问题) | | 输入区 | 麦克风 + 输入框 + 绿色发送按钮 | ### 消息气泡 | 类型 | 样式 | |------|------| | AI 消息 | 左对齐,机器人图标蓝色 `#3B82F6`,气泡白色 `#FFFFFF` | | 用户消息 | 右对齐,用户图标绿色 `#10B981`,气泡绿色 `#10B981`,文字白色 | | 时间 | 灰色 `#9CA3AF`,位于气泡下方 | ### 样式常量 ```typescript const chatStyles = { // 气泡 userBubble: { backgroundColor: '#10B981', borderRadius: 16, borderBottomRightRadius: 4, }, assistantBubble: { backgroundColor: '#FFFFFF', borderRadius: 16, borderBottomLeftRadius: 4, }, // 头像 userAvatar: { backgroundColor: '#10B981', }, aiAvatar: { backgroundColor: '#3B82F6', }, // 输入区 inputContainer: { backgroundColor: '#FFFFFF', borderTopColor: '#E5E7EB', }, sendButton: { backgroundColor: '#10B981', borderRadius: 20, }, } ``` --- ## 实现要点 1. **消息列表**:使用 FlatList 渲染消息,支持自动滚动到底部 2. **输入框**:固定在底部,支持多行输入 3. **键盘适配**:使用 KeyboardAvoidingView 处理键盘弹出 4. **消息气泡**:区分用户和 AI 消息样式 --- ## 关键代码示例 ### 对话列表页面 ```typescript // src/screens/chat/ChatListScreen.tsx import React, { useState, useEffect } from 'react' import { View, FlatList, StyleSheet, TouchableOpacity } from 'react-native' import { Text, FAB, Card, IconButton } from 'react-native-paper' import { useNavigation } from '@react-navigation/native' import dayjs from 'dayjs' import { getConversations, createConversation, deleteConversation } from '../../api/conversation' import type { Conversation } from '../../types' import type { ChatNavigationProp } from '../../navigation/types' const ChatListScreen = () => { const navigation = useNavigation() const [conversations, setConversations] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { loadConversations() }, []) const loadConversations = async () => { try { const data = await getConversations() setConversations(data) } finally { setLoading(false) } } const handleCreate = async () => { const conv = await createConversation() navigation.navigate('ChatDetail', { id: conv.id }) } const handleDelete = async (id: number) => { await deleteConversation(id) setConversations(conversations.filter((c) => c.id !== id)) } const renderItem = ({ item }: { item: Conversation }) => ( navigation.navigate('ChatDetail', { id: item.id })} > {item.title} {dayjs(item.updated_at).format('MM-DD HH:mm')} handleDelete(item.id)} /> ) return ( {conversations.length === 0 ? ( 暂无对话记录 点击下方按钮开始第一次对话 ) : ( item.id.toString()} contentContainerStyle={styles.list} /> )} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, list: { padding: 16, }, card: { marginBottom: 12, }, cardContent: { flexDirection: 'row', alignItems: 'center', }, cardInfo: { flex: 1, }, cardTitle: { fontSize: 16, fontWeight: '500', }, cardTime: { fontSize: 12, color: '#999', marginTop: 4, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, emptyText: { fontSize: 16, color: '#666', }, emptySubtext: { fontSize: 14, color: '#999', marginTop: 8, }, fab: { position: 'absolute', right: 16, bottom: 16, }, }) export default ChatListScreen ``` ### 对话详情页面 ```typescript // src/screens/chat/ChatDetailScreen.tsx import React, { useState, useEffect, useRef } from 'react' import { View, FlatList, StyleSheet, KeyboardAvoidingView, Platform, } from 'react-native' import { Text, TextInput, IconButton, Avatar } from 'react-native-paper' import { useRoute } from '@react-navigation/native' import dayjs from 'dayjs' import { getConversation, sendMessage } from '../../api/conversation' import { useUserStore } from '../../stores/userStore' import type { Message } from '../../types' import type { ChatDetailRouteProp } from '../../navigation/types' const ChatDetailScreen = () => { const route = useRoute() const { id } = route.params const { user } = useUserStore() const flatListRef = useRef(null) const [messages, setMessages] = useState([]) const [inputText, setInputText] = useState('') const [sending, setSending] = useState(false) const [loading, setLoading] = useState(true) useEffect(() => { loadMessages() }, []) const loadMessages = async () => { try { const data = await getConversation(id) setMessages(data.messages || []) } finally { setLoading(false) } } const handleSend = async () => { const content = inputText.trim() if (!content || sending) return // 添加用户消息 const userMessage: Message = { id: Date.now(), role: 'user', content, created_at: new Date().toISOString(), } setMessages((prev) => [...prev, userMessage]) setInputText('') setSending(true) try { const res = await sendMessage(id, content) const assistantMessage: Message = { id: Date.now() + 1, role: 'assistant', content: res.reply, created_at: new Date().toISOString(), } setMessages((prev) => [...prev, assistantMessage]) } catch (error) { // 移除用户消息 setMessages((prev) => prev.slice(0, -1)) setInputText(content) } finally { setSending(false) } } const renderMessage = ({ item }: { item: Message }) => { const isUser = item.role === 'user' return ( {!isUser && ( )} {item.content} {isUser && ( )} ) } return ( item.id.toString()} contentContainerStyle={styles.messageList} onContentSizeChange={() => flatListRef.current?.scrollToEnd()} /> {sending && ( AI 正在输入... )} AI 建议仅供参考,不构成医疗诊断 ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, messageList: { padding: 16, }, messageRow: { flexDirection: 'row', marginBottom: 16, alignItems: 'flex-end', }, messageRowUser: { flexDirection: 'row-reverse', }, avatar: { marginHorizontal: 8, }, messageBubble: { maxWidth: '70%', padding: 12, borderRadius: 16, }, userBubble: { backgroundColor: '#667eea', borderBottomRightRadius: 4, }, assistantBubble: { backgroundColor: '#fff', borderBottomLeftRadius: 4, }, userText: { color: '#fff', }, assistantText: { color: '#333', }, typingIndicator: { paddingHorizontal: 16, paddingVertical: 8, }, typingText: { color: '#999', fontSize: 13, }, inputContainer: { flexDirection: 'row', alignItems: 'center', padding: 8, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#eee', }, input: { flex: 1, maxHeight: 100, backgroundColor: '#f5f5f5', borderRadius: 20, paddingHorizontal: 16, }, disclaimer: { padding: 8, backgroundColor: '#fef0f0', alignItems: 'center', }, disclaimerText: { fontSize: 12, color: '#f56c6c', }, }) export default ChatDetailScreen ``` --- ## 需要创建的文件 | 文件路径 | 说明 | |----------|------| | `src/api/conversation.ts` | 对话 API | | `src/screens/chat/ChatListScreen.tsx` | 对话列表 | | `src/screens/chat/ChatDetailScreen.tsx` | 对话详情 | --- ## 验收标准 - [ ] 对话列表正常显示 - [ ] 新建和删除对话正常 - [ ] 消息发送和接收正常 - [ ] 键盘弹出时布局正常 - [ ] 免责声明显示 --- ## 预计耗时 30-40 分钟 --- ## 下一步 完成后进入 `04-APP开发/07-个人中心页面.md`