# 05-体质辨识页面 ## 目标 实现 APP 端中医体质辨识问卷和结果展示功能。 --- ## UI 设计参考 > 参考设计稿:`files/ui/体质页.png`、`files/ui/体质检测.png`、`files/ui/体质分析.png` ### 体质首页 | 区域 | 设计要点 | |------|----------| | 顶部卡片 | 绿色渐变背景 `#10B981 → #2EC4B6`,圆角 16px | | 标题 | "中医体质自测",白色 20px 粗体 | | 测试说明 | 白色卡片,3步骤(绿色序号圆圈) | | 按钮 | 绿色全宽按钮,圆角 24px | ### 问卷页面 | 区域 | 设计要点 | |------|----------| | 进度显示 | 右上角 "1/65",绿色进度条高度 4px | | 问题标签 | 绿色背景 `#DCFCE7`,文字 `#10B981` | | 选项按钮 | 白色背景,边框 `#E5E7EB`,选中时绿色边框 + 背景 | | 底部提示 | 浅绿色背景 `#ECFDF5`,info 图标 | ### 结果页面 | 区域 | 设计要点 | |------|----------| | 顶部 | 绿色渐变 + 人体轮廓 + 分享按钮 | | 体质名称 | 白色大字 32px,分数徽章 | | 雷达图 | 白色卡片,颜色 `#10B981`,透明填充 | | 调理建议 | 2×2 网格,各带彩色图标背景 | ### 调理建议卡片 | 类型 | 图标背景 | 图标颜色 | |------|----------|----------| | 起居 | `#EDE9FE` | `#8B5CF6` | | 饮食 | `#CCFBF1` | `#14B8A6` | | 运动 | `#EDE9FE` | `#8B5CF6` | | 情志 | `#FCE7F3` | `#EC4899` | --- ## 实现要点 1. **问卷 UI**:使用卡片+按钮组形式展示题目和选项 2. **进度条**:使用 `ProgressBar` 显示答题进度 3. **雷达图**:使用 `react-native-gifted-charts` 或 `victory-native` 4. **结果展示**:使用 Tab 切换调养建议 --- ## 关键代码示例 ### 问卷页面 ```typescript // src/screens/constitution/ConstitutionQuestionsScreen.tsx import React, { useState, useEffect } from 'react' import { View, Text, ScrollView, StyleSheet } from 'react-native' import { Button, ProgressBar, Card } from 'react-native-paper' import { useNavigation } from '@react-navigation/native' import { getQuestions, submitAssessment } from '../../api/constitution' import type { Question } from '../../types' const options = ['没有', '很少', '有时', '经常', '总是'] const ConstitutionQuestionsScreen = () => { const navigation = useNavigation() const [questions, setQuestions] = useState([]) const [currentIndex, setCurrentIndex] = useState(0) const [answers, setAnswers] = useState>({}) const [loading, setLoading] = useState(true) const [submitting, setSubmitting] = useState(false) useEffect(() => { loadQuestions() }, []) const loadQuestions = async () => { try { const data = await getQuestions() setQuestions(data) } finally { setLoading(false) } } const currentQuestion = questions[currentIndex] const progress = questions.length > 0 ? (currentIndex + 1) / questions.length : 0 const selectOption = (score: number) => { setAnswers({ ...answers, [currentQuestion.id]: score }) } const handleNext = () => { if (currentIndex < questions.length - 1) { setCurrentIndex(currentIndex + 1) } } const handlePrev = () => { if (currentIndex > 0) { setCurrentIndex(currentIndex - 1) } } const handleSubmit = async () => { setSubmitting(true) try { const answerList = Object.entries(answers).map(([qid, score]) => ({ question_id: parseInt(qid), score, })) await submitAssessment(answerList) navigation.navigate('ConstitutionResult') } finally { setSubmitting(false) } } if (loading || !currentQuestion) { return ( 加载中... ) } return ( {currentIndex + 1} / {questions.length} {currentIndex + 1}. {currentQuestion.question_text} {options.map((option, index) => ( ))} {currentIndex < questions.length - 1 ? ( ) : ( )} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, progressContainer: { padding: 16, backgroundColor: '#fff', }, progressText: { textAlign: 'center', marginBottom: 8, color: '#666', }, content: { flex: 1, padding: 16, }, questionCard: { marginBottom: 16, }, questionText: { fontSize: 18, lineHeight: 28, marginBottom: 20, }, options: { gap: 12, }, optionButton: { marginBottom: 8, }, navButtons: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#eee', }, }) export default ConstitutionQuestionsScreen ``` ### 结果页面(使用 victory-native 雷达图) ```typescript // src/screens/constitution/ConstitutionResultScreen.tsx import React, { useState, useEffect } from 'react' import { View, Text, ScrollView, StyleSheet } from 'react-native' import { Card, Chip, Button } from 'react-native-paper' import { useNavigation } from '@react-navigation/native' import { getLatestResult } from '../../api/constitution' import type { ConstitutionResult } from '../../types' const ConstitutionResultScreen = () => { const navigation = useNavigation() const [result, setResult] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { loadResult() }, []) const loadResult = async () => { try { const data = await getLatestResult() setResult(data) } finally { setLoading(false) } } if (loading || !result) { return ( 加载中... ) } return ( {/* 主要体质 */} 您的体质类型 {result.primary_constitution.name} {result.primary_constitution.description} {/* 所有体质得分 */} 体质得分 {result.all_scores.map((score) => ( {score.name} {score.score.toFixed(0)} ))} {/* 调养建议 */} 调养建议 {Object.entries(result.recommendations).map(([type, recs]) => ( {Object.entries(recs).map(([key, value]) => ( {key === 'diet' ? '饮食' : key === 'lifestyle' ? '起居' : key === 'exercise' ? '运动' : '情志'} {value} ))} ))} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', padding: 16, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, primaryCard: { marginBottom: 16, alignItems: 'center', }, card: { marginBottom: 16, }, sectionTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 12, }, primaryChip: { backgroundColor: '#667eea', alignSelf: 'center', marginBottom: 12, }, primaryChipText: { color: '#fff', fontSize: 16, }, description: { textAlign: 'center', color: '#666', lineHeight: 22, }, scoreItem: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, }, scoreName: { width: 60, fontSize: 13, }, scoreBar: { flex: 1, height: 8, backgroundColor: '#e0e0e0', borderRadius: 4, marginHorizontal: 8, }, scoreBarFill: { height: '100%', backgroundColor: '#667eea', borderRadius: 4, }, scoreValue: { width: 30, textAlign: 'right', fontSize: 13, }, recSection: { marginBottom: 16, }, recItem: { backgroundColor: '#f9f9f9', padding: 12, borderRadius: 8, marginBottom: 8, }, recTitle: { fontWeight: 'bold', marginBottom: 4, }, recText: { color: '#666', lineHeight: 20, }, actions: { padding: 16, alignItems: 'center', }, }) export default ConstitutionResultScreen ``` --- ## 需要创建的文件 | 文件路径 | 说明 | |----------|------| | `src/api/constitution.ts` | 体质 API | | `src/screens/constitution/ConstitutionHomeScreen.tsx` | 测评首页 | | `src/screens/constitution/ConstitutionQuestionsScreen.tsx` | 问卷页面 | | `src/screens/constitution/ConstitutionResultScreen.tsx` | 结果页面 | --- ## 验收标准 - [ ] 问卷正常加载和答题 - [ ] 进度条显示正确 - [ ] 提交后显示结果 - [ ] 调养建议完整显示 --- ## 预计耗时 35-45 分钟 --- ## 下一步 完成后进入 `04-APP开发/06-AI对话页面.md`