# 06-AI对话页面(原型) ## 目标 实现 APP 端 AI 健康问诊对话功能,使用模拟数据模拟多轮对话效果。 --- ## UI 设计参考 > 参考设计稿:`files/ui/问答页.png`、`files/ui/问答对话.png` --- ## 前置要求 - 导航配置完成 - 模拟数据服务已创建(`src/mock/chat.ts`) --- ## 实施步骤 ### 步骤 1:创建对话状态 Store 创建 `src/stores/useChatStore.ts`: ```typescript import { create } from 'zustand' import AsyncStorage from '@react-native-async-storage/async-storage' import { Conversation, Message } from '../types' interface ChatState { conversations: Conversation[] addConversation: (conv: Conversation) => void deleteConversation: (id: string) => void addMessage: (convId: string, message: Message) => void } export const useChatStore = create((set, get) => ({ conversations: [], addConversation: (conv) => { const updated = [conv, ...get().conversations] AsyncStorage.setItem('conversations', JSON.stringify(updated)) set({ conversations: updated }) }, deleteConversation: (id) => { const updated = get().conversations.filter((c) => c.id !== id) AsyncStorage.setItem('conversations', JSON.stringify(updated)) set({ conversations: updated }) }, addMessage: (convId, message) => { const updated = get().conversations.map((c) => { if (c.id === convId) { return { ...c, messages: [...c.messages, message], updatedAt: new Date().toISOString(), } } return c }) AsyncStorage.setItem('conversations', JSON.stringify(updated)) set({ conversations: updated }) }, })) ``` ### 步骤 2:对话列表页面 创建 `src/screens/chat/ChatListScreen.tsx`: ```typescript import React from 'react' import { View, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native' import { Text, FAB, Card, IconButton } from 'react-native-paper' import Icon from 'react-native-vector-icons/MaterialCommunityIcons' import { useNavigation } from '@react-navigation/native' import dayjs from 'dayjs' import { useChatStore } from '../../stores/useChatStore' import type { ChatNavigationProp } from '../../navigation/types' const ChatListScreen = () => { const navigation = useNavigation() const { conversations, addConversation, deleteConversation } = useChatStore() const handleCreate = () => { const newConv = { id: Date.now().toString(), title: '新对话', messages: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), } addConversation(newConv) navigation.navigate('ChatDetail', { id: newConv.id }) } const handleDelete = (id: string) => { Alert.alert('确认删除', '确定要删除这个对话吗?', [ { text: '取消', style: 'cancel' }, { text: '删除', style: 'destructive', onPress: () => deleteConversation(id) }, ]) } const renderItem = ({ item }: { item: typeof conversations[0] }) => ( navigation.navigate('ChatDetail', { id: item.id })}> {item.title} {dayjs(item.updatedAt).format('MM-DD HH:mm')} handleDelete(item.id)} /> ) return ( {conversations.length === 0 ? ( 暂无对话记录 点击下方按钮开始咨询 ) : ( item.id} contentContainerStyle={styles.list} /> )} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F3F4F6' }, list: { padding: 16 }, card: { marginBottom: 12, borderRadius: 12 }, cardContent: { flexDirection: 'row', alignItems: 'center' }, cardIcon: { width: 48, height: 48, borderRadius: 24, backgroundColor: '#ECFDF5', justifyContent: 'center', alignItems: 'center' }, cardInfo: { flex: 1, marginLeft: 12 }, cardTitle: { fontSize: 16, fontWeight: '500', color: '#1F2937' }, cardTime: { fontSize: 12, color: '#9CA3AF', marginTop: 4 }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, emptyText: { fontSize: 16, color: '#6B7280', marginTop: 16 }, emptySubtext: { fontSize: 14, color: '#9CA3AF', marginTop: 4 }, fab: { position: 'absolute', right: 16, bottom: 16, backgroundColor: '#10B981' }, }) export default ChatListScreen ``` ### 步骤 3:对话详情页面 创建 `src/screens/chat/ChatDetailScreen.tsx`: ```typescript import React, { useState, 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 { useChatStore } from '../../stores/useChatStore' import { useAuthStore } from '../../stores/useAuthStore' import { mockAIReply } from '../../mock/chat' import type { ChatDetailRouteProp } from '../../navigation/types' import type { Message } from '../../types' const ChatDetailScreen = () => { const route = useRoute() const { id } = route.params const { conversations, addMessage } = useChatStore() const { user } = useAuthStore() const flatListRef = useRef(null) const conversation = conversations.find((c) => c.id === id) const messages = conversation?.messages || [] const [inputText, setInputText] = useState('') const [sending, setSending] = useState(false) const handleSend = async () => { const content = inputText.trim() if (!content || sending) return // 添加用户消息 const userMessage: Message = { id: Date.now().toString(), role: 'user', content, createdAt: new Date().toISOString(), } addMessage(id, userMessage) setInputText('') // 模拟AI回复 setSending(true) try { const reply = await mockAIReply(content) const assistantMessage: Message = { id: (Date.now() + 1).toString(), role: 'assistant', content: reply, createdAt: new Date().toISOString(), } addMessage(id, assistantMessage) } finally { setSending(false) } } const renderMessage = ({ item }: { item: Message }) => { const isUser = item.role === 'user' return ( {!isUser && ( )} {item.content} {isUser && ( )} ) } return ( {/* 欢迎消息 */} {messages.length === 0 && ( AI健康助手 您好!我是AI健康助手,可以为您提供健康咨询和建议。 {'\n'}请描述您的症状或健康问题。 )} {/* 消息列表 */} item.id} contentContainerStyle={styles.messageList} onContentSizeChange={() => flatListRef.current?.scrollToEnd()} /> {/* 输入中提示 */} {sending && ( AI 正在思考... )} {/* 输入区域 */} {/* 免责声明 */} AI 建议仅供参考,不构成医疗诊断,如有需要请就医 ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F3F4F6' }, welcomeContainer: { alignItems: 'center', padding: 32 }, welcomeAvatar: { backgroundColor: '#3B82F6' }, welcomeTitle: { fontSize: 20, fontWeight: '600', marginTop: 16, color: '#1F2937' }, welcomeText: { fontSize: 14, color: '#6B7280', textAlign: 'center', marginTop: 8, lineHeight: 22 }, messageList: { padding: 16 }, messageRow: { flexDirection: 'row', marginBottom: 16, alignItems: 'flex-end' }, messageRowUser: { flexDirection: 'row-reverse' }, avatarAI: { backgroundColor: '#3B82F6', marginRight: 8 }, avatarUser: { backgroundColor: '#10B981', marginLeft: 8 }, messageBubble: { maxWidth: '70%', padding: 12, borderRadius: 16 }, userBubble: { backgroundColor: '#10B981', borderBottomRightRadius: 4 }, assistantBubble: { backgroundColor: '#fff', borderBottomLeftRadius: 4 }, userText: { color: '#fff', fontSize: 14, lineHeight: 20 }, assistantText: { color: '#1F2937', fontSize: 14, lineHeight: 20 }, typingIndicator: { paddingHorizontal: 16, paddingVertical: 8 }, typingText: { color: '#9CA3AF', fontSize: 13 }, inputContainer: { flexDirection: 'row', alignItems: 'center', padding: 8, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#E5E7EB' }, input: { flex: 1, maxHeight: 100, backgroundColor: '#F3F4F6', borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8 }, sendButton: { backgroundColor: '#10B981', marginLeft: 8 }, disclaimer: { padding: 8, backgroundColor: '#FEF3C7', alignItems: 'center' }, disclaimerText: { fontSize: 11, color: '#92400E' }, }) export default ChatDetailScreen ``` --- ## 模拟数据说明 使用 `src/mock/chat.ts` 中的 `mockAIReply` 函数: - 根据关键词匹配预设回答 - 支持:疲劳、失眠、关节痛等常见问题 - 回答格式包含:情况分析、建议、用药参考、产品推荐 --- ## 验收标准 - [ ] 对话列表正常显示 - [ ] 新建对话正常 - [ ] 删除对话正常 - [ ] 消息发送和模拟回复正常 - [ ] 消息气泡样式正确 - [ ] 免责声明显示 --- ## 预计耗时 35-45 分钟 --- ## 下一步 完成后进入 `02-APP原型开发/07-个人中心页面.md`