# 05-体质辨识页面(原型) ## 目标 实现 APP 端中医体质辨识问卷和结果展示,使用本地模拟数据计算结果。 --- ## UI 设计参考 > 参考设计稿:`files/ui/体质页.png`、`files/ui/体质检测.png`、`files/ui/体质分析.png` --- ## 页面组成 1. **体质首页** - 介绍页面,引导用户开始测试 2. **问卷页面** - 60道题目,逐题作答 3. **结果页面** - 显示体质类型、雷达图、调养建议 --- ## 前置要求 - 导航配置完成 - 模拟数据服务已创建(`src/mock/constitution.ts`) --- ## 实施步骤 ### 步骤 1:体质首页 创建 `src/screens/constitution/ConstitutionHomeScreen.tsx`: ```typescript import React from 'react' import { View, ScrollView, StyleSheet } from 'react-native' import { Text, Card, Button } from 'react-native-paper' import Icon from 'react-native-vector-icons/MaterialCommunityIcons' import { useNavigation } from '@react-navigation/native' import { useConstitutionStore } from '../../stores/useConstitutionStore' import { constitutionNames, constitutionDescriptions } from '../../mock/constitution' const ConstitutionHomeScreen = () => { const navigation = useNavigation() const { result } = useConstitutionStore() const steps = [ { icon: 'clipboard-text', title: '回答问卷', desc: '60道题目,约10分钟' }, { icon: 'calculator', title: '智能分析', desc: '根据答案计算体质' }, { icon: 'file-document', title: '获取报告', desc: '体质类型和调养建议' }, ] return ( {/* 已有结果时显示 */} {result && ( 您已完成体质测评 {constitutionNames[result.primaryType]} {constitutionDescriptions[result.primaryType].description} )} {/* 介绍卡片 */} 中医体质自测 中医体质辨识是以中医理论为指导,根据人体生理特点分为9种基本体质类型。 了解自己的体质类型,有助于选择适合的养生方法。 {/* 步骤说明 */} {steps.map((step, index) => ( {step.title} {step.desc} {index < steps.length - 1 && } ))} {/* 开始按钮 */} 建议每3-6个月重新测评一次,以跟踪体质变化 ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F3F4F6', padding: 16 }, resultCard: { marginBottom: 16, borderRadius: 12, borderLeftWidth: 4, borderLeftColor: '#10B981' }, resultHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 }, resultTitle: { fontSize: 16, fontWeight: '600', marginLeft: 8, color: '#10B981' }, resultBody: { marginBottom: 12 }, resultType: { fontSize: 24, fontWeight: 'bold', color: '#1F2937' }, resultDesc: { fontSize: 14, color: '#6B7280', marginTop: 4 }, resultButton: { borderRadius: 8 }, introCard: { marginBottom: 16, borderRadius: 12 }, introTitle: { fontSize: 20, fontWeight: 'bold', color: '#1F2937', marginBottom: 8 }, introDesc: { fontSize: 14, color: '#6B7280', lineHeight: 22 }, steps: { backgroundColor: '#fff', borderRadius: 12, padding: 16, marginBottom: 16 }, stepItem: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 16 }, stepIcon: { width: 48, height: 48, borderRadius: 24, backgroundColor: '#ECFDF5', justifyContent: 'center', alignItems: 'center' }, stepContent: { flex: 1, marginLeft: 12 }, stepTitle: { fontSize: 16, fontWeight: '600', color: '#1F2937' }, stepDesc: { fontSize: 13, color: '#6B7280', marginTop: 2 }, stepLine: { position: 'absolute', left: 24, top: 48, width: 1, height: 16, backgroundColor: '#E5E7EB' }, startButton: { borderRadius: 24, marginBottom: 12 }, startButtonContent: { paddingVertical: 8 }, note: { fontSize: 12, color: '#9CA3AF', textAlign: 'center' }, }) export default ConstitutionHomeScreen ``` ### 步骤 2:问卷页面 创建 `src/screens/constitution/ConstitutionQuestionsScreen.tsx`: ```typescript import React, { useState } from 'react' import { View, ScrollView, StyleSheet, Alert } from 'react-native' import { Text, Button, ProgressBar, Card } from 'react-native-paper' import { useNavigation } from '@react-navigation/native' import { useConstitutionStore } from '../../stores/useConstitutionStore' import { constitutionQuestions, calculateConstitution } from '../../mock/constitution' const ConstitutionQuestionsScreen = () => { const navigation = useNavigation() const { setResult } = useConstitutionStore() const [currentIndex, setCurrentIndex] = useState(0) const [answers, setAnswers] = useState>({}) const questions = constitutionQuestions const currentQuestion = questions[currentIndex] const progress = (currentIndex + 1) / questions.length const isLastQuestion = currentIndex === questions.length - 1 const selectOption = (value: number) => { setAnswers({ ...answers, [currentQuestion.id]: value }) } const handleNext = () => { if (!answers[currentQuestion.id]) { Alert.alert('提示', '请选择一个选项') return } if (currentIndex < questions.length - 1) { setCurrentIndex(currentIndex + 1) } } const handlePrev = () => { if (currentIndex > 0) { setCurrentIndex(currentIndex - 1) } } const handleSubmit = () => { if (Object.keys(answers).length < questions.length) { Alert.alert('提示', '请完成所有题目') return } // 本地计算结果 const result = calculateConstitution(answers) setResult(result) navigation.navigate('ConstitutionResult') } return ( {/* 进度条 */} 第 {currentIndex + 1} 题 / 共 {questions.length} 题 {/* 问题卡片 */} {currentQuestion.question} {currentQuestion.options.map((option) => ( ))} {/* 导航按钮 */} {isLastQuestion ? ( ) : ( )} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F3F4F6' }, progressContainer: { padding: 16, backgroundColor: '#fff' }, progressText: { textAlign: 'center', marginBottom: 8, color: '#6B7280' }, progressBar: { height: 6, borderRadius: 3 }, content: { flex: 1, padding: 16 }, questionCard: { borderRadius: 12 }, questionText: { fontSize: 18, lineHeight: 28, color: '#1F2937', marginBottom: 20 }, options: { gap: 12 }, optionButton: { marginBottom: 8, borderRadius: 8 }, navButtons: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#E5E7EB' }, navButton: { flex: 1, marginHorizontal: 8, borderRadius: 8 }, }) export default ConstitutionQuestionsScreen ``` ### 步骤 3:结果页面 创建 `src/screens/constitution/ConstitutionResultScreen.tsx`: ```typescript import React from 'react' import { View, ScrollView, StyleSheet } from 'react-native' import { Text, Card, Chip, Button } from 'react-native-paper' import Icon from 'react-native-vector-icons/MaterialCommunityIcons' import { useNavigation } from '@react-navigation/native' import { useConstitutionStore } from '../../stores/useConstitutionStore' import { constitutionNames, constitutionDescriptions } from '../../mock/constitution' import { getProductsByConstitution } from '../../mock/products' const ConstitutionResultScreen = () => { const navigation = useNavigation() const { result } = useConstitutionStore() if (!result) { return ( 暂无测评结果 ) } const info = constitutionDescriptions[result.primaryType] const products = getProductsByConstitution(result.primaryType) // 计算所有体质得分用于显示 const allScores = Object.entries(result.scores) .map(([type, score]) => ({ type, name: constitutionNames[type as keyof typeof constitutionNames], score, })) .sort((a, b) => b.score - a.score) return ( {/* 主体质卡片 */} {constitutionNames[result.primaryType]} {result.scores[result.primaryType]}分 {info.description} {/* 体质得分 */} {allScores.map((item) => ( {item.name} {item.score} ))} {/* 体质特征 */} {info.features.map((feature, index) => ( {feature} ))} {/* 调养建议 */} {info.suggestions.map((suggestion, index) => ( {suggestion} ))} {/* 推荐产品 */} {products.map((product) => ( {product.name} ¥{product.price} ))} {/* 操作按钮 */} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F3F4F6', padding: 16 }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, primaryCard: { borderRadius: 16, marginBottom: 16 }, primaryContent: { alignItems: 'center', paddingVertical: 24 }, primaryType: { fontSize: 28, fontWeight: 'bold', color: '#1F2937', marginTop: 12 }, primaryScore: { fontSize: 18, color: '#10B981', marginTop: 4 }, primaryDesc: { fontSize: 14, color: '#6B7280', marginTop: 12, textAlign: 'center', lineHeight: 22 }, card: { borderRadius: 12, marginBottom: 16 }, scoreItem: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 }, scoreName: { width: 60, fontSize: 13 }, scoreBar: { flex: 1, height: 8, backgroundColor: '#E5E7EB', borderRadius: 4, marginHorizontal: 8 }, scoreBarFill: { height: '100%', backgroundColor: '#10B981', borderRadius: 4 }, scoreValue: { width: 30, textAlign: 'right', fontSize: 13 }, tagList: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, tag: { backgroundColor: '#ECFDF5' }, suggestionItem: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 12 }, suggestionText: { flex: 1, marginLeft: 8, fontSize: 14, color: '#4B5563', lineHeight: 20 }, productItem: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#E5E7EB' }, productName: { fontSize: 14, color: '#1F2937' }, productPrice: { fontSize: 14, color: '#EF4444', fontWeight: '600' }, actions: { padding: 16 }, actionButton: { borderRadius: 24 }, }) export default ConstitutionResultScreen ``` --- ## 验收标准 - [ ] 体质首页正常显示 - [ ] 问卷60题可完整答题 - [ ] 进度条显示正确 - [ ] 提交后本地计算结果 - [ ] 结果页显示体质类型和建议 - [ ] 体质得分分布正确 --- ## 预计耗时 40-50 分钟 --- ## 下一步 完成后进入 `02-APP原型开发/06-AI对话页面.md`