feat(challenges): 新增 ChallengeProgressCard 组件并接入喝水挑战进度上报
- 抽离进度卡片为独立组件,支持主题色自定义与复用 - 挑战列表页顶部展示进行中的挑战进度 - 喝水记录自动上报至关联的水挑战 - 移除旧版 challengeSlice 与冗余进度样式 - 统一使用 value 字段上报进度,兼容多类型挑战
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { ChallengeType } from '@/services/challengesApi';
|
||||
import { reportChallengeProgress, selectChallengeList } from '@/store/challengesSlice';
|
||||
import { deleteWaterIntakeFromHealthKit, getWaterIntakeFromHealthKit, saveWaterIntakeToHealthKit } from '@/utils/health';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { Toast } from '@/utils/toast.utils';
|
||||
@@ -41,6 +44,32 @@ function createDateRange(date: string): { startDate: string; endDate: string } {
|
||||
};
|
||||
}
|
||||
|
||||
const useWaterChallengeProgressReporter = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const allChallenges = useAppSelector(selectChallengeList);
|
||||
const joinedWaterChallenges = useMemo(
|
||||
() => allChallenges.filter((challenge) => challenge.type === ChallengeType.WATER && challenge.isJoined),
|
||||
[allChallenges]
|
||||
);
|
||||
|
||||
return useCallback(
|
||||
async (value: number) => {
|
||||
if (!joinedWaterChallenges.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const challenge of joinedWaterChallenges) {
|
||||
try {
|
||||
await dispatch(reportChallengeProgress({ id: challenge.id, value })).unwrap();
|
||||
} catch (error) {
|
||||
console.warn('挑战进度上报失败', { error, challengeId: challenge.id });
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch, joinedWaterChallenges]
|
||||
);
|
||||
};
|
||||
|
||||
export const useWaterData = () => {
|
||||
// 本地状态管理
|
||||
const [loading, setLoading] = useState({
|
||||
@@ -152,46 +181,53 @@ export const useWaterData = () => {
|
||||
}, [getWaterRecordsByDate]);
|
||||
|
||||
// 创建喝水记录
|
||||
const addWaterRecord = useCallback(async (amount: number, recordedAt?: string) => {
|
||||
try {
|
||||
const recordTime = recordedAt || dayjs().toISOString();
|
||||
const reportWaterChallengeProgress = useWaterChallengeProgressReporter();
|
||||
|
||||
// 保存到 HealthKit
|
||||
const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordTime);
|
||||
if (!healthKitSuccess) {
|
||||
Toast.error('保存到 HealthKit 失败');
|
||||
const addWaterRecord = useCallback(
|
||||
async (amount: number, recordedAt?: string) => {
|
||||
try {
|
||||
const recordTime = recordedAt || dayjs().toISOString();
|
||||
const date = dayjs(recordTime).format('YYYY-MM-DD');
|
||||
const isToday = dayjs(recordTime).isSame(dayjs(), 'day');
|
||||
|
||||
// 保存到 HealthKit
|
||||
const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordTime);
|
||||
if (!healthKitSuccess) {
|
||||
Toast.error('保存到 HealthKit 失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重新获取当前日期的数据以刷新界面
|
||||
const updatedRecords = await getWaterRecordsByDate(date);
|
||||
const totalAmount = updatedRecords.reduce((sum, record) => sum + record.amount, 0);
|
||||
|
||||
// 如果是今天的数据,更新Widget
|
||||
if (isToday) {
|
||||
const quickAddAmount = await getQuickWaterAmount();
|
||||
|
||||
try {
|
||||
await syncWaterDataToWidget({
|
||||
currentIntake: totalAmount,
|
||||
dailyGoal: dailyWaterGoal,
|
||||
quickAddAmount,
|
||||
});
|
||||
await refreshWidget();
|
||||
} catch (widgetError) {
|
||||
console.error('Widget 同步错误:', widgetError);
|
||||
}
|
||||
}
|
||||
|
||||
await reportWaterChallengeProgress(totalAmount);
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('添加喝水记录失败:', error);
|
||||
Toast.error(error?.message || '添加喝水记录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重新获取当前日期的数据以刷新界面
|
||||
const date = dayjs(recordTime).format('YYYY-MM-DD');
|
||||
await getWaterRecordsByDate(date);
|
||||
|
||||
// 如果是今天的数据,更新Widget
|
||||
if (date === dayjs().format('YYYY-MM-DD')) {
|
||||
const todayRecords = waterRecords[date] || [];
|
||||
const totalAmount = todayRecords.reduce((sum, record) => sum + record.amount, 0);
|
||||
const quickAddAmount = await getQuickWaterAmount();
|
||||
|
||||
try {
|
||||
await syncWaterDataToWidget({
|
||||
currentIntake: totalAmount,
|
||||
dailyGoal: dailyWaterGoal,
|
||||
quickAddAmount,
|
||||
});
|
||||
await refreshWidget();
|
||||
} catch (widgetError) {
|
||||
console.error('Widget 同步错误:', widgetError);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('添加喝水记录失败:', error);
|
||||
Toast.error(error?.message || '添加喝水记录失败');
|
||||
return false;
|
||||
}
|
||||
}, [getWaterRecordsByDate, waterRecords, dailyWaterGoal]);
|
||||
},
|
||||
[dailyWaterGoal, getWaterRecordsByDate, reportWaterChallengeProgress]
|
||||
);
|
||||
|
||||
// 更新喝水记录(HealthKit不支持更新,只能删除后重新添加)
|
||||
const updateWaterRecord = useCallback(async (id: string, amount?: number, note?: string, recordedAt?: string) => {
|
||||
@@ -524,44 +560,51 @@ export const useWaterDataByDate = (targetDate?: string) => {
|
||||
}, []);
|
||||
|
||||
// 创建喝水记录
|
||||
const addWaterRecord = useCallback(async (amount: number, recordedAt?: string) => {
|
||||
try {
|
||||
const recordTime = recordedAt || dayjs().toISOString();
|
||||
const reportWaterChallengeProgress = useWaterChallengeProgressReporter();
|
||||
|
||||
// 保存到 HealthKit
|
||||
const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordTime);
|
||||
if (!healthKitSuccess) {
|
||||
Toast.error('保存到 HealthKit 失败');
|
||||
const addWaterRecord = useCallback(
|
||||
async (amount: number, recordedAt?: string) => {
|
||||
try {
|
||||
const recordTime = recordedAt || dayjs().toISOString();
|
||||
|
||||
// 保存到 HealthKit
|
||||
const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordTime);
|
||||
if (!healthKitSuccess) {
|
||||
Toast.error('保存到 HealthKit 失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重新获取当前日期的数据以刷新界面
|
||||
const updatedRecords = await getWaterRecordsByDate(dateToUse);
|
||||
const totalAmount = updatedRecords.reduce((sum, record) => sum + record.amount, 0);
|
||||
|
||||
// 如果是今天的数据,更新Widget
|
||||
if (dateToUse === dayjs().format('YYYY-MM-DD')) {
|
||||
const quickAddAmount = await getQuickWaterAmount();
|
||||
|
||||
try {
|
||||
await syncWaterDataToWidget({
|
||||
currentIntake: totalAmount,
|
||||
dailyGoal: dailyWaterGoal,
|
||||
quickAddAmount,
|
||||
});
|
||||
await refreshWidget();
|
||||
} catch (widgetError) {
|
||||
console.error('Widget 同步错误:', widgetError);
|
||||
}
|
||||
}
|
||||
|
||||
await reportWaterChallengeProgress(totalAmount);
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('添加喝水记录失败:', error);
|
||||
Toast.error(error?.message || '添加喝水记录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重新获取当前日期的数据以刷新界面
|
||||
await getWaterRecordsByDate(dateToUse);
|
||||
|
||||
// 如果是今天的数据,更新Widget
|
||||
if (dateToUse === dayjs().format('YYYY-MM-DD')) {
|
||||
const totalAmount = waterRecords.reduce((sum, record) => sum + record.amount, 0) + amount;
|
||||
const quickAddAmount = await getQuickWaterAmount();
|
||||
|
||||
try {
|
||||
await syncWaterDataToWidget({
|
||||
currentIntake: totalAmount,
|
||||
dailyGoal: dailyWaterGoal,
|
||||
quickAddAmount,
|
||||
});
|
||||
await refreshWidget();
|
||||
} catch (widgetError) {
|
||||
console.error('Widget 同步错误:', widgetError);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('添加喝水记录失败:', error);
|
||||
Toast.error(error?.message || '添加喝水记录失败');
|
||||
return false;
|
||||
}
|
||||
}, [getWaterRecordsByDate, dateToUse, waterRecords, dailyWaterGoal]);
|
||||
},
|
||||
[dailyWaterGoal, dateToUse, getWaterRecordsByDate, reportWaterChallengeProgress]
|
||||
);
|
||||
|
||||
// 更新喝水记录
|
||||
const updateWaterRecord = useCallback(async (id: string, amount?: number, note?: string, recordedAt?: string) => {
|
||||
@@ -708,4 +751,4 @@ export const useWaterDataByDate = (targetDate?: string) => {
|
||||
updateWaterGoal,
|
||||
getWaterRecordsByDate,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user