feat(个人中心): 优化会员横幅组件,支持深色模式与国际化;新增医疗记录卡片组件,完善健康档案功能
This commit is contained in:
@@ -16,7 +16,10 @@ import {
|
||||
joinFamilyGroup,
|
||||
selectFamilyGroup,
|
||||
} from '@/store/familyHealthSlice';
|
||||
import { selectHealthHistoryProgress } from '@/store/healthSlice';
|
||||
import {
|
||||
fetchHealthHistory,
|
||||
selectHealthHistoryProgress
|
||||
} from '@/store/healthSlice';
|
||||
import { DEFAULT_MEMBER_NAME } from '@/store/userSlice';
|
||||
import { Toast } from '@/utils/toast.utils';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
@@ -25,7 +28,7 @@ import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Stack, useRouter } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { Pressable, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export default function HealthProfileScreen() {
|
||||
@@ -41,12 +44,39 @@ export default function HealthProfileScreen() {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [joinModalVisible, setJoinModalVisible] = useState(false);
|
||||
const [inviteCodeInput, setInviteCodeInput] = useState('');
|
||||
const [relationshipInput, setRelationshipInput] = useState('');
|
||||
const [selectedRelationship, setSelectedRelationship] = useState('');
|
||||
const [isJoining, setIsJoining] = useState(false);
|
||||
const [joinError, setJoinError] = useState<string | null>(null);
|
||||
|
||||
// Redux state
|
||||
const familyGroup = useAppSelector(selectFamilyGroup);
|
||||
const medicalRecords = useAppSelector((state) => state.health.medicalRecords);
|
||||
const records = medicalRecords?.records || [];
|
||||
const prescriptions = medicalRecords?.prescriptions || [];
|
||||
|
||||
// Calculate Medical Records Count
|
||||
const medicalRecordsCount = useMemo(() => records.length + prescriptions.length, [records, prescriptions]);
|
||||
|
||||
// 亲属关系选项
|
||||
const relationshipOptions = useMemo(() => [
|
||||
{ key: 'spouse', label: t('familyGroup.relationships.spouse') },
|
||||
{ key: 'father', label: t('familyGroup.relationships.father') },
|
||||
{ key: 'mother', label: t('familyGroup.relationships.mother') },
|
||||
{ key: 'son', label: t('familyGroup.relationships.son') },
|
||||
{ key: 'daughter', label: t('familyGroup.relationships.daughter') },
|
||||
{ key: 'grandfather', label: t('familyGroup.relationships.grandfather') },
|
||||
{ key: 'grandmother', label: t('familyGroup.relationships.grandmother') },
|
||||
{ key: 'grandson', label: t('familyGroup.relationships.grandson') },
|
||||
{ key: 'granddaughter', label: t('familyGroup.relationships.granddaughter') },
|
||||
{ key: 'brother', label: t('familyGroup.relationships.brother') },
|
||||
{ key: 'sister', label: t('familyGroup.relationships.sister') },
|
||||
{ key: 'uncle', label: t('familyGroup.relationships.uncle') },
|
||||
{ key: 'aunt', label: t('familyGroup.relationships.aunt') },
|
||||
{ key: 'nephew', label: t('familyGroup.relationships.nephew') },
|
||||
{ key: 'niece', label: t('familyGroup.relationships.niece') },
|
||||
{ key: 'cousin', label: t('familyGroup.relationships.cousin') },
|
||||
{ key: 'other', label: t('familyGroup.relationships.other') },
|
||||
], [t]);
|
||||
|
||||
// Mock user data - in a real app this would come from Redux/Context
|
||||
const userProfile = useAppSelector((state) => state.user.profile);
|
||||
@@ -79,16 +109,17 @@ export default function HealthProfileScreen() {
|
||||
return Math.round((filledCount / totalFields) * 100);
|
||||
}, [userProfile.height, userProfile.weight, userProfile.waistCircumference]);
|
||||
|
||||
// 初始化获取家庭组信息
|
||||
// 初始化获取家庭组信息和健康史数据
|
||||
useEffect(() => {
|
||||
dispatch(fetchFamilyGroup());
|
||||
dispatch(fetchHealthHistory());
|
||||
}, [dispatch]);
|
||||
|
||||
// 重置弹窗状态
|
||||
useEffect(() => {
|
||||
if (!joinModalVisible) {
|
||||
setInviteCodeInput('');
|
||||
setRelationshipInput('');
|
||||
setSelectedRelationship('');
|
||||
setJoinError(null);
|
||||
}
|
||||
}, [joinModalVisible]);
|
||||
@@ -107,32 +138,34 @@ export default function HealthProfileScreen() {
|
||||
if (!ok) return;
|
||||
|
||||
const code = inviteCodeInput.trim().toUpperCase();
|
||||
const relationship = relationshipInput.trim();
|
||||
|
||||
if (!code) {
|
||||
setJoinError('请输入邀请码');
|
||||
setJoinError(t('familyGroup.errors.emptyCode'));
|
||||
return;
|
||||
}
|
||||
if (!relationship) {
|
||||
setJoinError('请输入与创建者的关系');
|
||||
if (!selectedRelationship) {
|
||||
setJoinError(t('familyGroup.errors.emptyRelationship'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取选中关系的显示文本
|
||||
const relationshipLabel = relationshipOptions.find(r => r.key === selectedRelationship)?.label || selectedRelationship;
|
||||
|
||||
setIsJoining(true);
|
||||
setJoinError(null);
|
||||
|
||||
try {
|
||||
await dispatch(joinFamilyGroup({ inviteCode: code, relationship })).unwrap();
|
||||
await dispatch(joinFamilyGroup({ inviteCode: code, relationship: relationshipLabel })).unwrap();
|
||||
await dispatch(fetchFamilyGroup());
|
||||
setJoinModalVisible(false);
|
||||
Toast.success('成功加入家庭组');
|
||||
Toast.success(t('familyGroup.success'));
|
||||
} catch (error) {
|
||||
const message = typeof error === 'string' ? error : '加入失败,请检查邀请码是否正确';
|
||||
setJoinError(message);
|
||||
} finally {
|
||||
setIsJoining(false);
|
||||
}
|
||||
}, [dispatch, ensureLoggedIn, inviteCodeInput, isJoining, relationshipInput]);
|
||||
}, [dispatch, ensureLoggedIn, inviteCodeInput, isJoining, selectedRelationship, relationshipOptions, t]);
|
||||
|
||||
const gradientColors: [string, string] =
|
||||
theme === 'dark'
|
||||
@@ -149,7 +182,7 @@ export default function HealthProfileScreen() {
|
||||
const tabIcons = ["person", "time", "folder", "clipboard", "medkit"];
|
||||
|
||||
const handleTabPress = (index: number) => {
|
||||
if (index === 4) {
|
||||
if (index === 3) {
|
||||
// Handle Medicine Box tab specially
|
||||
router.push('/medications/manage-medications');
|
||||
return;
|
||||
@@ -245,7 +278,7 @@ export default function HealthProfileScreen() {
|
||||
title={t('health.tabs.healthProfile.medicalRecords')}
|
||||
progress={0}
|
||||
gradientColors={['#E0E7FF', '#C7D2FE']}
|
||||
label="0"
|
||||
label={medicalRecordsCount.toString()}
|
||||
suffix="份"
|
||||
/>
|
||||
</View>
|
||||
@@ -307,16 +340,17 @@ export default function HealthProfileScreen() {
|
||||
visible={joinModalVisible}
|
||||
onClose={() => setJoinModalVisible(false)}
|
||||
onConfirm={handleSubmitJoin}
|
||||
title="加入家庭组"
|
||||
description="输入家人分享的邀请码,加入家庭健康管理"
|
||||
confirmText={isJoining ? '加入中...' : '加入'}
|
||||
cancelText="取消"
|
||||
title={t('familyGroup.joinTitle')}
|
||||
description={t('familyGroup.joinDescription')}
|
||||
confirmText={isJoining ? t('familyGroup.joining') : t('familyGroup.joinButton')}
|
||||
cancelText={t('familyGroup.cancel')}
|
||||
loading={isJoining}
|
||||
content={
|
||||
<View style={styles.modalInputWrapper}>
|
||||
<View style={styles.joinModalContent}>
|
||||
{/* 邀请码输入 */}
|
||||
<TextInput
|
||||
style={styles.modalInput}
|
||||
placeholder="请输入邀请码"
|
||||
style={styles.inviteCodeInput}
|
||||
placeholder={t('familyGroup.inviteCodePlaceholder')}
|
||||
placeholderTextColor="#9ca3af"
|
||||
value={inviteCodeInput}
|
||||
onChangeText={(text) => setInviteCodeInput(text.toUpperCase())}
|
||||
@@ -325,15 +359,43 @@ export default function HealthProfileScreen() {
|
||||
keyboardType="default"
|
||||
maxLength={12}
|
||||
/>
|
||||
<TextInput
|
||||
style={[styles.modalInput, { marginTop: 12 }]}
|
||||
placeholder="与创建者的关系(如:配偶、父母、子女)"
|
||||
placeholderTextColor="#9ca3af"
|
||||
value={relationshipInput}
|
||||
onChangeText={setRelationshipInput}
|
||||
autoCorrect={false}
|
||||
maxLength={20}
|
||||
/>
|
||||
|
||||
{/* 关系选择标签 */}
|
||||
<Text style={styles.relationshipLabel}>{t('familyGroup.relationshipLabel')}</Text>
|
||||
|
||||
{/* 关系选项网格 - 固定高度可滚动 */}
|
||||
<ScrollView
|
||||
style={styles.relationshipScrollView}
|
||||
contentContainerStyle={styles.relationshipGrid}
|
||||
showsVerticalScrollIndicator={true}
|
||||
nestedScrollEnabled
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{relationshipOptions.map((option) => {
|
||||
const isSelected = selectedRelationship === option.key;
|
||||
return (
|
||||
<Pressable
|
||||
key={option.key}
|
||||
style={[
|
||||
styles.relationshipChip,
|
||||
isSelected && styles.relationshipChipSelected,
|
||||
]}
|
||||
onPress={() => setSelectedRelationship(option.key)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.relationshipChipText,
|
||||
isSelected && styles.relationshipChipTextSelected,
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{joinError && joinModalVisible ? (
|
||||
<Text style={styles.modalError}>{joinError}</Text>
|
||||
) : null}
|
||||
@@ -493,24 +555,62 @@ const styles = StyleSheet.create({
|
||||
joinButtonFallback: {
|
||||
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||
},
|
||||
modalInputWrapper: {
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
backgroundColor: '#f8fafc',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
gap: 6,
|
||||
// 加入家庭组弹窗样式
|
||||
joinModalContent: {
|
||||
gap: 12,
|
||||
},
|
||||
modalInput: {
|
||||
paddingVertical: 12,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 0.5,
|
||||
inviteCodeInput: {
|
||||
backgroundColor: '#f8fafc',
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
letterSpacing: 2,
|
||||
color: '#0f1528',
|
||||
textAlign: 'center',
|
||||
},
|
||||
relationshipLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#374151',
|
||||
marginTop: 4,
|
||||
marginBottom: 2,
|
||||
},
|
||||
relationshipScrollView: {
|
||||
maxHeight: 160,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#fafafa',
|
||||
},
|
||||
relationshipGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
padding: 8,
|
||||
},
|
||||
relationshipChip: {
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#f3f4f6',
|
||||
borderWidth: 1.5,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
relationshipChipSelected: {
|
||||
backgroundColor: '#ede9fe',
|
||||
borderColor: '#8b5cf6',
|
||||
},
|
||||
relationshipChipText: {
|
||||
fontSize: 14,
|
||||
color: '#6b7280',
|
||||
fontWeight: '500',
|
||||
},
|
||||
relationshipChipTextSelected: {
|
||||
color: '#7c3aed',
|
||||
fontWeight: '600',
|
||||
},
|
||||
modalError: {
|
||||
marginTop: 10,
|
||||
marginTop: 6,
|
||||
fontSize: 12,
|
||||
color: '#ef4444',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user