feat: 新增任务管理功能及相关组件

- 将目标页面改为任务列表,支持任务的创建、完成和跳过功能
- 新增任务卡片和任务进度卡片组件,展示任务状态和进度
- 实现任务数据的获取和管理,集成Redux状态管理
- 更新API服务,支持任务相关的CRUD操作
- 编写任务管理功能实现文档,详细描述功能和组件架构
This commit is contained in:
richarjiang
2025-08-22 17:30:14 +08:00
parent 231620d778
commit 259f10540e
21 changed files with 2756 additions and 608 deletions

114
services/healthData.ts Normal file
View File

@@ -0,0 +1,114 @@
import { Platform } from 'react-native';
import {
HKQuantityTypeIdentifier,
HKQuantitySample,
getMostRecentQuantitySample,
isAvailable,
authorize,
} from 'react-native-health';
interface HealthData {
oxygenSaturation: number | null;
heartRate: number | null;
lastUpdated: Date | null;
}
class HealthDataService {
private static instance: HealthDataService;
private isAuthorized = false;
private constructor() {}
public static getInstance(): HealthDataService {
if (!HealthDataService.instance) {
HealthDataService.instance = new HealthDataService();
}
return HealthDataService.instance;
}
async requestAuthorization(): Promise<boolean> {
if (Platform.OS !== 'ios') {
return false;
}
try {
const available = await isAvailable();
if (!available) {
return false;
}
const permissions = [
{
type: HKQuantityTypeIdentifier.OxygenSaturation,
access: 'read' as const
},
{
type: HKQuantityTypeIdentifier.HeartRate,
access: 'read' as const
}
];
const authorized = await authorize(permissions);
this.isAuthorized = authorized;
return authorized;
} catch (error) {
console.error('Health data authorization error:', error);
return false;
}
}
async getOxygenSaturation(): Promise<number | null> {
if (!this.isAuthorized) {
return null;
}
try {
const sample: HKQuantitySample | null = await getMostRecentQuantitySample(
HKQuantityTypeIdentifier.OxygenSaturation
);
if (sample) {
return Number(sample.value.toFixed(1));
}
return null;
} catch (error) {
console.error('Error reading oxygen saturation:', error);
return null;
}
}
async getHeartRate(): Promise<number | null> {
if (!this.isAuthorized) {
return null;
}
try {
const sample: HKQuantitySample | null = await getMostRecentQuantitySample(
HKQuantityTypeIdentifier.HeartRate
);
if (sample) {
return Math.round(sample.value);
}
return null;
} catch (error) {
console.error('Error reading heart rate:', error);
return null;
}
}
async getHealthData(): Promise<HealthData> {
const [oxygenSaturation, heartRate] = await Promise.all([
this.getOxygenSaturation(),
this.getHeartRate()
]);
return {
oxygenSaturation,
heartRate,
lastUpdated: new Date()
};
}
}
export default HealthDataService.getInstance();

83
services/tasksApi.ts Normal file
View File

@@ -0,0 +1,83 @@
import {
ApiResponse,
CompleteTaskRequest,
GetTasksQuery,
PaginatedResponse,
SkipTaskRequest,
Task,
TaskListItem,
TaskStats,
} from '@/types/goals';
import { api } from './api';
// 任务管理API服务
/**
* 获取任务列表
*/
export const getTasks = async (query: GetTasksQuery = {}): Promise<PaginatedResponse<TaskListItem>> => {
const searchParams = new URLSearchParams();
Object.entries(query).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
const queryString = searchParams.toString();
const path = queryString ? `/goals/tasks?${queryString}` : '/goals/tasks';
return api.get<PaginatedResponse<TaskListItem>>(path);
};
/**
* 获取特定目标的任务列表
*/
export const getTasksByGoalId = async (goalId: string, query: GetTasksQuery = {}): Promise<PaginatedResponse<TaskListItem>> => {
const searchParams = new URLSearchParams();
Object.entries(query).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
const queryString = searchParams.toString();
const path = queryString ? `/goals/${goalId}/tasks?${queryString}` : `/goals/${goalId}/tasks`;
return api.get<PaginatedResponse<TaskListItem>>(path);
};
/**
* 完成任务
*/
export const completeTask = async (taskId: string, completionData: CompleteTaskRequest = {}): Promise<Task> => {
return api.post<Task>(`/goals/tasks/${taskId}/complete`, completionData);
};
/**
* 跳过任务
*/
export const skipTask = async (taskId: string, skipData: SkipTaskRequest = {}): Promise<Task> => {
return api.post<Task>(`/goals/tasks/${taskId}/skip`, skipData);
};
/**
* 获取任务统计
*/
export const getTaskStats = async (goalId?: string): Promise<TaskStats> => {
const path = goalId ? `/goals/tasks/stats/overview?goalId=${goalId}` : '/goals/tasks/stats/overview';
const response = await api.get<ApiResponse<TaskStats>>(path);
return response.data;
};
// 导出所有API方法
export const tasksApi = {
getTasks,
getTasksByGoalId,
completeTask,
skipTask,
getTaskStats,
};
export default tasksApi;