feat(medical): 添加医疗免责声明和参考文献功能
- 在用药模块首次添加时显示医疗免责声明弹窗 - 新增断食参考文献页面,展示权威医学机构来源 - 在个人中心添加WHO医学来源入口 - 使用本地存储记录用户已读免责声明状态 - 支持Liquid Glass毛玻璃效果和降级方案 - 新增中英文国际化翻译支持
This commit is contained in:
@@ -48,6 +48,7 @@ import {
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
@@ -592,6 +593,38 @@ export default function FastingTabScreen() {
|
||||
activePlanId={activePlan?.id ?? currentPlan?.id}
|
||||
onSelectPlan={handleSelectPlan}
|
||||
/>
|
||||
|
||||
{/* 参考文献入口 */}
|
||||
<View style={styles.referencesSection}>
|
||||
<TouchableOpacity
|
||||
style={styles.referencesButton}
|
||||
onPress={() => router.push(ROUTES.FASTING_REFERENCES)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<GlassView
|
||||
style={styles.referencesGlass}
|
||||
glassEffectStyle="clear"
|
||||
tintColor="rgba(46, 49, 66, 0.05)"
|
||||
isInteractive={true}
|
||||
>
|
||||
<View style={styles.referencesContent}>
|
||||
<Ionicons name="library-outline" size={20} color="#2E3142" />
|
||||
<Text style={styles.referencesText}>参考文献与医学来源</Text>
|
||||
<Ionicons name="chevron-forward" size={16} color="#6F7D87" />
|
||||
</View>
|
||||
</GlassView>
|
||||
) : (
|
||||
<View style={[styles.referencesGlass, styles.referencesFallback]}>
|
||||
<View style={styles.referencesContent}>
|
||||
<Ionicons name="library-outline" size={20} color="#2E3142" />
|
||||
<Text style={styles.referencesText}>参考文献与医学来源</Text>
|
||||
<Ionicons name="chevron-forward" size={16} color="#6F7D87" />
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<FastingStartPickerModal
|
||||
@@ -766,4 +799,34 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '600',
|
||||
color: '#2E3142',
|
||||
},
|
||||
referencesSection: {
|
||||
marginTop: 24,
|
||||
marginBottom: 20,
|
||||
},
|
||||
referencesButton: {
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
referencesGlass: {
|
||||
borderRadius: 20,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
referencesFallback: {
|
||||
backgroundColor: 'rgba(246, 248, 250, 0.8)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(46, 49, 66, 0.1)',
|
||||
},
|
||||
referencesContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
referencesText: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#2E3142',
|
||||
marginLeft: 12,
|
||||
marginRight: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,12 +3,14 @@ import { DateSelector } from '@/components/DateSelector';
|
||||
import { MedicationCard } from '@/components/medication/MedicationCard';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { MedicalDisclaimerSheet } from '@/components/ui/MedicalDisclaimerSheet';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { medicationNotificationService } from '@/services/medicationNotifications';
|
||||
import { fetchMedicationRecords, fetchMedications, selectMedicationDisplayItemsByDate } from '@/store/medicationsSlice';
|
||||
import { DEFAULT_MEMBER_NAME } from '@/store/userSlice';
|
||||
import { getItemSync, setItemSync } from '@/utils/kvStore';
|
||||
import { convertMedicationDataToWidget, refreshWidget, syncMedicationDataToWidget } from '@/utils/widgetDataSync';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
@@ -29,6 +31,9 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
// 本地存储键名:医疗免责声明已读状态
|
||||
const MEDICAL_DISCLAIMER_READ_KEY = 'medical_disclaimer_read';
|
||||
|
||||
type MedicationFilter = 'all' | 'taken' | 'missed';
|
||||
|
||||
type ThemeColors = (typeof Colors)[keyof typeof Colors];
|
||||
@@ -46,15 +51,37 @@ export default function MedicationsScreen() {
|
||||
const celebrationRef = useRef<CelebrationAnimationRef>(null);
|
||||
const celebrationTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [isCelebrationVisible, setIsCelebrationVisible] = useState(false);
|
||||
const [disclaimerVisible, setDisclaimerVisible] = useState(false);
|
||||
|
||||
// 从 Redux 获取数据
|
||||
const selectedKey = selectedDate.format('YYYY-MM-DD');
|
||||
const medicationsForDay = useAppSelector((state) => selectMedicationDisplayItemsByDate(selectedKey)(state));
|
||||
|
||||
const handleOpenAddMedication = useCallback(() => {
|
||||
// 检查是否已经读过免责声明
|
||||
const hasRead = getItemSync(MEDICAL_DISCLAIMER_READ_KEY);
|
||||
|
||||
if (hasRead === 'true') {
|
||||
// 已读过,直接跳转
|
||||
router.push('/medications/add-medication');
|
||||
} else {
|
||||
// 未读过,显示医疗免责声明弹窗
|
||||
setDisclaimerVisible(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDisclaimerConfirm = useCallback(() => {
|
||||
// 用户同意免责声明后,记录已读状态,关闭弹窗并跳转到添加页面
|
||||
setItemSync(MEDICAL_DISCLAIMER_READ_KEY, 'true');
|
||||
setDisclaimerVisible(false);
|
||||
router.push('/medications/add-medication');
|
||||
}, []);
|
||||
|
||||
const handleDisclaimerClose = useCallback(() => {
|
||||
// 用户不接受免责声明,只关闭弹窗,不跳转,不记录已读状态
|
||||
setDisclaimerVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleOpenMedicationManagement = useCallback(() => {
|
||||
router.push('/medications/manage-medications');
|
||||
}, []);
|
||||
@@ -328,6 +355,13 @@ export default function MedicationsScreen() {
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{/* 医疗免责声明弹窗 */}
|
||||
<MedicalDisclaimerSheet
|
||||
visible={disclaimerVisible}
|
||||
onClose={handleDisclaimerClose}
|
||||
onConfirm={handleDisclaimerConfirm}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -435,6 +435,16 @@ export default function PersonalScreen() {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('personal.sections.medicalSources'),
|
||||
items: [
|
||||
{
|
||||
icon: 'medkit-outline' as React.ComponentProps<typeof Ionicons>['name'],
|
||||
title: t('personal.menu.whoSource'),
|
||||
onPress: () => Linking.openURL('https://www.who.int'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('personal.language.title'),
|
||||
items: [
|
||||
|
||||
Reference in New Issue
Block a user