- 创建medicineExtension小组件,支持iOS桌面显示用药计划 - 实现App Group数据共享机制,支持主应用与小组件数据同步 - 添加AppGroupUserDefaultsManager原生模块,提供跨应用数据访问能力 - 添加WidgetManager和WidgetCenterHelper,实现小组件刷新控制 - 在medications页面和Redux store中集成小组件数据同步逻辑 - 支持实时同步今日用药状态(待服用/已服用/已错过)到小组件 - 配置App Group entitlements (group.com.anonymous.digitalpilates) - 更新Xcode项目配置,添加WidgetKit和SwiftUI框架支持
531 lines
17 KiB
TypeScript
531 lines
17 KiB
TypeScript
import AsyncStorage from '@/utils/kvStore';
|
||
import dayjs from 'dayjs';
|
||
import { NativeModules } from 'react-native';
|
||
|
||
// Widget数据同步服务
|
||
// 用于在主App和iOS Widget之间同步饮水数据
|
||
|
||
// App Group标识符
|
||
const APP_GROUP_ID = 'group.com.anonymous.digitalpilates';
|
||
|
||
// Widget数据存储键
|
||
const WIDGET_DATA_KEYS = {
|
||
CURRENT_WATER_INTAKE: 'widget_current_water_intake',
|
||
DAILY_WATER_GOAL: 'widget_daily_water_goal',
|
||
QUICK_ADD_AMOUNT: 'widget_quick_add_amount',
|
||
LAST_SYNC_TIME: 'widget_last_sync_time',
|
||
PENDING_WATER_RECORDS: 'widget_pending_water_records',
|
||
} as const;
|
||
|
||
// 用药计划Widget数据存储键
|
||
const MEDICATION_WIDGET_KEYS = {
|
||
MEDICATION_DATA: 'widget_medication_data',
|
||
PENDING_MEDICATION_ACTIONS: 'widget_pending_medication_actions',
|
||
MEDICATION_LAST_SYNC: 'widget_medication_last_sync',
|
||
} as const;
|
||
|
||
export interface WidgetWaterData {
|
||
currentIntake: number;
|
||
dailyGoal: number;
|
||
quickAddAmount: number;
|
||
lastSyncTime: string;
|
||
}
|
||
|
||
// 默认数据
|
||
const DEFAULT_WIDGET_DATA: WidgetWaterData = {
|
||
currentIntake: 0,
|
||
dailyGoal: 2000,
|
||
quickAddAmount: 150,
|
||
lastSyncTime: new Date().toISOString(),
|
||
};
|
||
|
||
// 用药计划小组件数据接口
|
||
export interface WidgetMedicationData {
|
||
medications: WidgetMedicationItem[];
|
||
lastSyncTime: string;
|
||
date: string; // YYYY-MM-DD格式的日期
|
||
}
|
||
|
||
export interface WidgetMedicationItem {
|
||
id: string;
|
||
name: string;
|
||
dosage: string;
|
||
scheduledTime: string; // HH:mm格式
|
||
status: 'upcoming' | 'taken' | 'missed';
|
||
medicationId: string;
|
||
recordId?: string;
|
||
image?: string; // 图片URI或默认图片资源名
|
||
}
|
||
|
||
// 默认用药数据
|
||
const DEFAULT_MEDICATION_WIDGET_DATA: WidgetMedicationData = {
|
||
medications: [],
|
||
lastSyncTime: new Date().toISOString(),
|
||
date: new Date().toISOString().split('T')[0], // 格式化为YYYY-MM-DD
|
||
};
|
||
|
||
/**
|
||
* 创建Native Module来访问App Group UserDefaults
|
||
* 这需要在iOS原生代码中实现
|
||
*/
|
||
interface AppGroupUserDefaults {
|
||
setString: (groupId: string, key: string, value: string) => Promise<void>;
|
||
getString: (groupId: string, key: string) => Promise<string | null>;
|
||
setNumber: (groupId: string, key: string, value: number) => Promise<void>;
|
||
getNumber: (groupId: string, key: string) => Promise<number>;
|
||
removeKey: (groupId: string, key: string) => Promise<void>;
|
||
setArray: (groupId: string, key: string, value: any[]) => Promise<void>;
|
||
getArray: (groupId: string, key: string) => Promise<any[] | null>;
|
||
}
|
||
|
||
// Widget待同步水记录接口
|
||
export interface PendingWaterRecord {
|
||
amount: number;
|
||
recordedAt: string;
|
||
source: string;
|
||
widgetAdded: boolean;
|
||
}
|
||
|
||
// 尝试获取原生模块,如果不存在则使用fallback
|
||
const AppGroupDefaults: AppGroupUserDefaults | null = NativeModules.AppGroupUserDefaultsManager || null;
|
||
|
||
// 添加调试日志
|
||
if (AppGroupDefaults) {
|
||
console.log('✅ AppGroupUserDefaultsManager native module loaded successfully');
|
||
} else {
|
||
console.warn('⚠️ AppGroupUserDefaultsManager native module not found, will use AsyncStorage fallback');
|
||
}
|
||
|
||
/**
|
||
* 将饮水数据同步到Widget
|
||
*/
|
||
export const syncWaterDataToWidget = async (data: Partial<WidgetWaterData>): Promise<void> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
console.warn('AppGroupUserDefaults native module not available, falling back to AsyncStorage');
|
||
// Fallback到AsyncStorage (仅主App可用)
|
||
const currentData = await getWidgetDataFromAsyncStorage();
|
||
const updatedData = { ...currentData, ...data, lastSyncTime: new Date().toISOString() };
|
||
await saveWidgetDataToAsyncStorage(updatedData);
|
||
return;
|
||
}
|
||
|
||
// 使用App Group UserDefaults
|
||
if (data.currentIntake !== undefined) {
|
||
await AppGroupDefaults.setNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE, data.currentIntake);
|
||
}
|
||
|
||
if (data.dailyGoal !== undefined) {
|
||
await AppGroupDefaults.setNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.DAILY_WATER_GOAL, data.dailyGoal);
|
||
}
|
||
|
||
if (data.quickAddAmount !== undefined) {
|
||
await AppGroupDefaults.setNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT, data.quickAddAmount);
|
||
}
|
||
|
||
// 更新同步时间
|
||
await AppGroupDefaults.setString(APP_GROUP_ID, WIDGET_DATA_KEYS.LAST_SYNC_TIME, new Date().toISOString());
|
||
|
||
console.log('Widget data synced successfully:', data);
|
||
} catch (error) {
|
||
console.error('Failed to sync data to widget:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 从Widget获取饮水数据
|
||
*/
|
||
export const getWidgetWaterData = async (): Promise<WidgetWaterData> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
console.warn('AppGroupUserDefaults native module not available, falling back to AsyncStorage');
|
||
return await getWidgetDataFromAsyncStorage();
|
||
}
|
||
|
||
// 从App Group UserDefaults读取数据
|
||
const currentIntake = await AppGroupDefaults.getNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE);
|
||
const dailyGoal = await AppGroupDefaults.getNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.DAILY_WATER_GOAL);
|
||
const quickAddAmount = await AppGroupDefaults.getNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT);
|
||
const lastSyncTime = await AppGroupDefaults.getString(APP_GROUP_ID, WIDGET_DATA_KEYS.LAST_SYNC_TIME);
|
||
|
||
return {
|
||
currentIntake: currentIntake || DEFAULT_WIDGET_DATA.currentIntake,
|
||
dailyGoal: dailyGoal || DEFAULT_WIDGET_DATA.dailyGoal,
|
||
quickAddAmount: quickAddAmount || DEFAULT_WIDGET_DATA.quickAddAmount,
|
||
lastSyncTime: lastSyncTime || DEFAULT_WIDGET_DATA.lastSyncTime,
|
||
};
|
||
} catch (error) {
|
||
console.error('Failed to get widget data:', error);
|
||
return DEFAULT_WIDGET_DATA;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 清除Widget数据
|
||
*/
|
||
export const clearWidgetData = async (): Promise<void> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
await AsyncStorage.multiRemove(Object.values(WIDGET_DATA_KEYS));
|
||
return;
|
||
}
|
||
|
||
// 清除App Group UserDefaults中的数据
|
||
await Promise.all([
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE),
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.DAILY_WATER_GOAL),
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT),
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.LAST_SYNC_TIME),
|
||
]);
|
||
|
||
console.log('Widget data cleared successfully');
|
||
} catch (error) {
|
||
console.error('Failed to clear widget data:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Fallback: 使用AsyncStorage存储Widget数据
|
||
*/
|
||
const saveWidgetDataToAsyncStorage = async (data: WidgetWaterData): Promise<void> => {
|
||
const dataToStore: [string, string][] = [
|
||
[WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE, data.currentIntake.toString()],
|
||
[WIDGET_DATA_KEYS.DAILY_WATER_GOAL, data.dailyGoal.toString()],
|
||
[WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT, data.quickAddAmount.toString()],
|
||
[WIDGET_DATA_KEYS.LAST_SYNC_TIME, data.lastSyncTime],
|
||
];
|
||
|
||
await AsyncStorage.multiSet(dataToStore);
|
||
};
|
||
|
||
/**
|
||
* Fallback: 从AsyncStorage读取Widget数据
|
||
*/
|
||
const getWidgetDataFromAsyncStorage = async (): Promise<WidgetWaterData> => {
|
||
const keys = Object.values(WIDGET_DATA_KEYS);
|
||
const values = await AsyncStorage.multiGet(keys);
|
||
|
||
const data: any = {};
|
||
values.forEach(([key, value]) => {
|
||
if (value !== null) {
|
||
if (key === WIDGET_DATA_KEYS.LAST_SYNC_TIME) {
|
||
data[key] = value;
|
||
} else {
|
||
data[key] = parseInt(value, 10);
|
||
}
|
||
}
|
||
});
|
||
|
||
return {
|
||
currentIntake: data[WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE] || DEFAULT_WIDGET_DATA.currentIntake,
|
||
dailyGoal: data[WIDGET_DATA_KEYS.DAILY_WATER_GOAL] || DEFAULT_WIDGET_DATA.dailyGoal,
|
||
quickAddAmount: data[WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT] || DEFAULT_WIDGET_DATA.quickAddAmount,
|
||
lastSyncTime: data[WIDGET_DATA_KEYS.LAST_SYNC_TIME] || DEFAULT_WIDGET_DATA.lastSyncTime,
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 触发Widget刷新
|
||
* 通知iOS系统更新Widget
|
||
*/
|
||
export const refreshWidget = async (): Promise<void> => {
|
||
try {
|
||
if (NativeModules.WidgetManager?.reloadTimelines) {
|
||
await NativeModules.WidgetManager.reloadTimelines();
|
||
console.log('Widget refresh triggered');
|
||
} else {
|
||
console.warn('WidgetManager native module not available');
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to refresh widget:', error);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 获取待同步的水记录
|
||
*/
|
||
export const getPendingWaterRecords = async (): Promise<PendingWaterRecord[]> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
// Fallback: 从 AsyncStorage 读取
|
||
const pendingRecordsJson = await AsyncStorage.getItem(WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
|
||
return pendingRecordsJson ? JSON.parse(pendingRecordsJson) : [];
|
||
}
|
||
|
||
// 从 App Group UserDefaults 读取
|
||
const pendingRecords = await AppGroupDefaults.getArray(APP_GROUP_ID, WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
|
||
return pendingRecords || [];
|
||
} catch (error) {
|
||
console.error('Failed to get pending water records:', error);
|
||
return [];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 清除待同步的水记录
|
||
*/
|
||
export const clearPendingWaterRecords = async (): Promise<void> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
// Fallback: 从 AsyncStorage 清除
|
||
await AsyncStorage.removeItem(WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
|
||
return;
|
||
}
|
||
|
||
// 从 App Group UserDefaults 清除
|
||
await AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
|
||
console.log('Pending water records cleared successfully');
|
||
} catch (error) {
|
||
console.error('Failed to clear pending water records:', error);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 从Widget同步待处理的数据更改到主App
|
||
* 检查Widget中的数据更改并同步到主App的数据存储
|
||
*/
|
||
export const syncPendingWidgetChanges = async (): Promise<{
|
||
hasPendingChanges: boolean;
|
||
pendingRecords?: PendingWaterRecord[];
|
||
lastSyncTime?: string;
|
||
}> => {
|
||
try {
|
||
// 获取待同步的水记录
|
||
const pendingRecords = await getPendingWaterRecords();
|
||
|
||
if (pendingRecords.length > 0) {
|
||
console.log(`发现 ${pendingRecords.length} 条待同步的水记录`);
|
||
return {
|
||
hasPendingChanges: true,
|
||
pendingRecords,
|
||
lastSyncTime: new Date().toISOString(),
|
||
};
|
||
}
|
||
|
||
return { hasPendingChanges: false };
|
||
} catch (error) {
|
||
console.error('Failed to sync pending widget changes:', error);
|
||
return { hasPendingChanges: false };
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 标记Widget数据已被主App同步
|
||
*/
|
||
export const markWidgetDataSynced = async (): Promise<void> => {
|
||
try {
|
||
const currentTime = new Date().toISOString();
|
||
await AsyncStorage.setItem('last_app_widget_sync', currentTime);
|
||
} catch (error) {
|
||
console.error('Failed to mark widget data as synced:', error);
|
||
}
|
||
};
|
||
|
||
// ==================== 用药计划数据同步 ====================
|
||
|
||
/**
|
||
* 将用药数据同步到Widget
|
||
*/
|
||
export const syncMedicationDataToWidget = async (data: WidgetMedicationData): Promise<void> => {
|
||
try {
|
||
console.log('🔄 [Widget Sync] Starting medication data sync...');
|
||
console.log('🔄 [Widget Sync] Data to sync:', {
|
||
medicationsCount: data.medications.length,
|
||
date: data.date,
|
||
lastSyncTime: data.lastSyncTime,
|
||
medications: data.medications.map(m => ({
|
||
name: m.name,
|
||
scheduledTime: m.scheduledTime,
|
||
status: m.status
|
||
}))
|
||
});
|
||
|
||
if (!AppGroupDefaults) {
|
||
console.warn('⚠️ [Widget Sync] AppGroupUserDefaults native module not available, falling back to AsyncStorage');
|
||
// Fallback到AsyncStorage
|
||
const dataJson = JSON.stringify(data);
|
||
await AsyncStorage.setItem(MEDICATION_WIDGET_KEYS.MEDICATION_DATA, dataJson);
|
||
await AsyncStorage.setItem(MEDICATION_WIDGET_KEYS.MEDICATION_LAST_SYNC, data.lastSyncTime);
|
||
console.log('✅ [Widget Sync] Data saved to AsyncStorage (fallback)');
|
||
return;
|
||
}
|
||
|
||
// 使用App Group UserDefaults
|
||
const dataJson = JSON.stringify(data);
|
||
console.log('📝 [Widget Sync] JSON payload length:', dataJson.length);
|
||
|
||
await AppGroupDefaults.setString(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_DATA, dataJson);
|
||
await AppGroupDefaults.setString(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_LAST_SYNC, data.lastSyncTime);
|
||
|
||
console.log('✅ [Widget Sync] Medication widget data synced successfully:', data.medications.length, 'medications');
|
||
|
||
// 验证数据是否写入成功
|
||
const verifyJson = await AppGroupDefaults.getString(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_DATA);
|
||
if (verifyJson) {
|
||
console.log('✅ [Widget Sync] Verification successful - data exists in App Group');
|
||
} else {
|
||
console.warn('⚠️ [Widget Sync] Verification failed - data not found in App Group');
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ [Widget Sync] Failed to sync medication data to widget:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 从Widget获取用药数据
|
||
*/
|
||
export const getMedicationDataFromWidget = async (): Promise<WidgetMedicationData> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
console.warn('AppGroupUserDefaults native module not available, falling back to AsyncStorage');
|
||
// Fallback从AsyncStorage读取
|
||
const dataJson = await AsyncStorage.getItem(MEDICATION_WIDGET_KEYS.MEDICATION_DATA);
|
||
const lastSyncTime = await AsyncStorage.getItem(MEDICATION_WIDGET_KEYS.MEDICATION_LAST_SYNC);
|
||
|
||
if (dataJson) {
|
||
const data = JSON.parse(dataJson);
|
||
return {
|
||
...data,
|
||
lastSyncTime: lastSyncTime || DEFAULT_MEDICATION_WIDGET_DATA.lastSyncTime,
|
||
};
|
||
}
|
||
|
||
return DEFAULT_MEDICATION_WIDGET_DATA;
|
||
}
|
||
|
||
// 从App Group UserDefaults读取数据
|
||
const dataJson = await AppGroupDefaults.getString(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_DATA);
|
||
const lastSyncTime = await AppGroupDefaults.getString(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_LAST_SYNC);
|
||
|
||
if (dataJson) {
|
||
const data = JSON.parse(dataJson);
|
||
return {
|
||
...data,
|
||
lastSyncTime: lastSyncTime || DEFAULT_MEDICATION_WIDGET_DATA.lastSyncTime,
|
||
};
|
||
}
|
||
|
||
return DEFAULT_MEDICATION_WIDGET_DATA;
|
||
} catch (error) {
|
||
console.error('Failed to get medication widget data:', error);
|
||
return DEFAULT_MEDICATION_WIDGET_DATA;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 清除用药Widget数据
|
||
*/
|
||
export const clearMedicationWidgetData = async (): Promise<void> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
await AsyncStorage.multiRemove([
|
||
MEDICATION_WIDGET_KEYS.MEDICATION_DATA,
|
||
MEDICATION_WIDGET_KEYS.MEDICATION_LAST_SYNC,
|
||
MEDICATION_WIDGET_KEYS.PENDING_MEDICATION_ACTIONS,
|
||
]);
|
||
return;
|
||
}
|
||
|
||
// 清除App Group UserDefaults中的数据
|
||
await Promise.all([
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_DATA),
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.MEDICATION_LAST_SYNC),
|
||
AppGroupDefaults.removeKey(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.PENDING_MEDICATION_ACTIONS),
|
||
]);
|
||
|
||
console.log('Medication widget data cleared successfully');
|
||
} catch (error) {
|
||
console.error('Failed to clear medication widget data:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Widget用药操作接口
|
||
*/
|
||
export interface WidgetMedicationAction {
|
||
type: 'take' | 'skip';
|
||
recordId: string;
|
||
medicationId: string;
|
||
timestamp: string;
|
||
}
|
||
|
||
/**
|
||
* 从Widget获取待处理的用药操作
|
||
*/
|
||
export const getPendingMedicationActions = async (): Promise<WidgetMedicationAction[]> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
// Fallback: 从 AsyncStorage 读取
|
||
const pendingActionsJson = await AsyncStorage.getItem(MEDICATION_WIDGET_KEYS.PENDING_MEDICATION_ACTIONS);
|
||
return pendingActionsJson ? JSON.parse(pendingActionsJson) : [];
|
||
}
|
||
|
||
// 从 App Group UserDefaults 读取
|
||
const pendingActions = await AppGroupDefaults.getArray(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.PENDING_MEDICATION_ACTIONS);
|
||
return pendingActions || [];
|
||
} catch (error) {
|
||
console.error('Failed to get pending medication actions:', error);
|
||
return [];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 清除待处理的用药操作
|
||
*/
|
||
export const clearPendingMedicationActions = async (): Promise<void> => {
|
||
try {
|
||
if (!AppGroupDefaults) {
|
||
// Fallback: 从 AsyncStorage 清除
|
||
await AsyncStorage.removeItem(MEDICATION_WIDGET_KEYS.PENDING_MEDICATION_ACTIONS);
|
||
return;
|
||
}
|
||
|
||
// 从 App Group UserDefaults 清除
|
||
await AppGroupDefaults.removeKey(APP_GROUP_ID, MEDICATION_WIDGET_KEYS.PENDING_MEDICATION_ACTIONS);
|
||
console.log('Pending medication actions cleared successfully');
|
||
} catch (error) {
|
||
console.error('Failed to clear pending medication actions:', error);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 将Redux数据转换为Widget数据格式
|
||
*/
|
||
export const convertMedicationDataToWidget = (
|
||
records: import('@/types/medication').MedicationRecord[],
|
||
medications: import('@/types/medication').Medication[],
|
||
date: string
|
||
): WidgetMedicationData => {
|
||
const medicationMap = new Map(medications.map(m => [m.id, m]));
|
||
|
||
const widgetItems: WidgetMedicationItem[] = records
|
||
.map(record => {
|
||
const medication = medicationMap.get(record.medicationId);
|
||
if (!medication || !medication.isActive) return null;
|
||
|
||
return {
|
||
id: record.id,
|
||
name: medication.name,
|
||
dosage: `${medication.dosageValue} ${medication.dosageUnit}`,
|
||
scheduledTime: dayjs(record.scheduledTime).format('HH:mm'),
|
||
status: record.status,
|
||
medicationId: medication.id,
|
||
recordId: record.id,
|
||
image: medication.photoUrl,
|
||
};
|
||
})
|
||
.filter(Boolean) as WidgetMedicationItem[];
|
||
|
||
// 按时间排序
|
||
widgetItems.sort((a, b) => a.scheduledTime.localeCompare(b.scheduledTime));
|
||
|
||
return {
|
||
medications: widgetItems,
|
||
lastSyncTime: new Date().toISOString(),
|
||
date,
|
||
};
|
||
}; |