Search K
Appearance
Appearance
📊 SEO元描述:2024年最新JavaScript智能待办事项管理器核心功能实现教程,详解任务CRUD操作、任务分类标签、任务提醒功能。包含完整的React+TypeScript实现,适合前端开发者快速掌握现代Web应用开发。
核心关键词:JavaScript任务管理系统2024、React CRUD操作、任务分类标签、任务提醒功能、前端项目实战、TypeScript开发
长尾关键词:JavaScript任务管理怎么实现、React待办事项应用、任务CRUD操作开发、前端项目实战教程、TypeScript项目开发
通过本节JavaScript核心功能实现教程,你将系统性掌握:
任务管理系统的核心在于高效的数据管理和用户交互体验。我们将构建一个基于React+TypeScript的现代化任务管理应用,实现完整的任务生命周期管理,也是现代前端开发的完整实践案例。
💡 架构设计理念:采用模块化设计,每个模块职责单一,通过清晰的接口进行通信,确保代码的可维护性和可扩展性。
// 🎉 完整的数据模型定义
// 基础类型定义
export interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
}
// 用户模型
export interface User extends BaseEntity {
email: string;
name: string;
avatar?: string;
preferences: UserPreferences;
}
export interface UserPreferences {
theme: 'light' | 'dark' | 'system';
language: string;
timezone: string;
notifications: NotificationSettings;
defaultView: 'list' | 'board' | 'calendar';
}
export interface NotificationSettings {
email: boolean;
browser: boolean;
sound: boolean;
reminderMinutes: number[];
}
// 任务模型
export interface Task extends BaseEntity {
title: string;
description?: string;
status: TaskStatus;
priority: TaskPriority;
dueDate?: Date;
completedAt?: Date;
categoryId?: string;
tags: string[];
subtasks: Subtask[];
reminders: Reminder[];
attachments: Attachment[];
userId: string;
assignedTo?: string[];
estimatedTime?: number; // 预估时间(分钟)
actualTime?: number; // 实际时间(分钟)
progress: number; // 进度百分比 0-100
}
export enum TaskStatus {
TODO = 'todo',
IN_PROGRESS = 'in_progress',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
ON_HOLD = 'on_hold'
}
export enum TaskPriority {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
URGENT = 'urgent'
}
// 子任务模型
export interface Subtask extends BaseEntity {
title: string;
completed: boolean;
taskId: string;
order: number;
}
// 分类模型
export interface Category extends BaseEntity {
name: string;
color: string;
icon?: string;
description?: string;
userId: string;
parentId?: string; // 支持嵌套分类
order: number;
}
// 标签模型
export interface Tag extends BaseEntity {
name: string;
color: string;
userId: string;
usageCount: number;
}
// 提醒模型
export interface Reminder extends BaseEntity {
taskId: string;
type: ReminderType;
triggerTime: Date;
message?: string;
isActive: boolean;
isSent: boolean;
repeatPattern?: RepeatPattern;
}
export enum ReminderType {
ABSOLUTE = 'absolute', // 绝对时间提醒
RELATIVE = 'relative', // 相对时间提醒
LOCATION = 'location', // 位置提醒
RECURRING = 'recurring' // 重复提醒
}
export interface RepeatPattern {
type: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'custom';
interval: number;
daysOfWeek?: number[];
endDate?: Date;
maxOccurrences?: number;
}
// 附件模型
export interface Attachment extends BaseEntity {
name: string;
url: string;
type: string;
size: number;
taskId: string;
}
// API响应类型
export interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
timestamp: Date;
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
// 查询参数类型
export interface TaskQueryParams {
page?: number;
limit?: number;
status?: TaskStatus[];
priority?: TaskPriority[];
categoryId?: string;
tags?: string[];
search?: string;
sortBy?: 'createdAt' | 'updatedAt' | 'dueDate' | 'priority' | 'title';
sortOrder?: 'asc' | 'desc';
dueDateFrom?: Date;
dueDateTo?: Date;
}
// 统计数据类型
export interface TaskStatistics {
total: number;
completed: number;
inProgress: number;
overdue: number;
completionRate: number;
averageCompletionTime: number;
productivityTrend: ProductivityData[];
categoryDistribution: CategoryStats[];
priorityDistribution: PriorityStats[];
}
export interface ProductivityData {
date: Date;
tasksCompleted: number;
timeSpent: number;
efficiency: number;
}
export interface CategoryStats {
categoryId: string;
categoryName: string;
taskCount: number;
completedCount: number;
completionRate: number;
}
export interface PriorityStats {
priority: TaskPriority;
count: number;
percentage: number;
}// 🎉 任务管理服务实现
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
// 任务状态管理
interface TaskState {
tasks: Task[];
categories: Category[];
tags: Tag[];
loading: boolean;
error: string | null;
filters: TaskQueryParams;
selectedTask: Task | null;
statistics: TaskStatistics | null;
}
interface TaskActions {
// 任务CRUD操作
fetchTasks: (params?: TaskQueryParams) => Promise<void>;
createTask: (task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>) => Promise<Task>;
updateTask: (id: string, updates: Partial<Task>) => Promise<Task>;
deleteTask: (id: string) => Promise<void>;
duplicateTask: (id: string) => Promise<Task>;
// 批量操作
bulkUpdateTasks: (ids: string[], updates: Partial<Task>) => Promise<void>;
bulkDeleteTasks: (ids: string[]) => Promise<void>;
// 任务状态管理
toggleTaskStatus: (id: string) => Promise<void>;
updateTaskProgress: (id: string, progress: number) => Promise<void>;
// 子任务管理
addSubtask: (taskId: string, subtask: Omit<Subtask, 'id' | 'createdAt' | 'updatedAt' | 'taskId'>) => Promise<void>;
updateSubtask: (taskId: string, subtaskId: string, updates: Partial<Subtask>) => Promise<void>;
deleteSubtask: (taskId: string, subtaskId: string) => Promise<void>;
toggleSubtask: (taskId: string, subtaskId: string) => Promise<void>;
// 分类管理
fetchCategories: () => Promise<void>;
createCategory: (category: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>) => Promise<Category>;
updateCategory: (id: string, updates: Partial<Category>) => Promise<Category>;
deleteCategory: (id: string) => Promise<void>;
// 标签管理
fetchTags: () => Promise<void>;
createTag: (tag: Omit<Tag, 'id' | 'createdAt' | 'updatedAt' | 'usageCount'>) => Promise<Tag>;
updateTag: (id: string, updates: Partial<Tag>) => Promise<Tag>;
deleteTag: (id: string) => Promise<void>;
// 筛选和搜索
setFilters: (filters: Partial<TaskQueryParams>) => void;
clearFilters: () => void;
searchTasks: (query: string) => Promise<void>;
// 统计数据
fetchStatistics: () => Promise<void>;
// UI状态管理
setSelectedTask: (task: Task | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
clearError: () => void;
}
// API服务类
class TaskApiService {
private baseUrl = '/api/tasks';
async fetchTasks(params: TaskQueryParams = {}): Promise<PaginatedResponse<Task>> {
const queryString = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
value.forEach(v => queryString.append(key, v.toString()));
} else {
queryString.append(key, value.toString());
}
}
});
const response = await fetch(`${this.baseUrl}?${queryString}`);
if (!response.ok) {
throw new Error(`Failed to fetch tasks: ${response.statusText}`);
}
return response.json();
}
async createTask(task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<ApiResponse<Task>> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
if (!response.ok) {
throw new Error(`Failed to create task: ${response.statusText}`);
}
return response.json();
}
async updateTask(id: string, updates: Partial<Task>): Promise<ApiResponse<Task>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updates),
});
if (!response.ok) {
throw new Error(`Failed to update task: ${response.statusText}`);
}
return response.json();
}
async deleteTask(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`Failed to delete task: ${response.statusText}`);
}
return response.json();
}
async bulkUpdateTasks(ids: string[], updates: Partial<Task>): Promise<ApiResponse<Task[]>> {
const response = await fetch(`${this.baseUrl}/bulk`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids, updates }),
});
if (!response.ok) {
throw new Error(`Failed to bulk update tasks: ${response.statusText}`);
}
return response.json();
}
async fetchStatistics(): Promise<ApiResponse<TaskStatistics>> {
const response = await fetch(`${this.baseUrl}/statistics`);
if (!response.ok) {
throw new Error(`Failed to fetch statistics: ${response.statusText}`);
}
return response.json();
}
}
// Zustand状态管理实现
export const useTaskStore = create<TaskState & TaskActions>()(
devtools(
persist(
immer((set, get) => ({
// 初始状态
tasks: [],
categories: [],
tags: [],
loading: false,
error: null,
filters: {},
selectedTask: null,
statistics: null,
// API服务实例
apiService: new TaskApiService(),
// 任务CRUD操作
fetchTasks: async (params = {}) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const response = await get().apiService.fetchTasks(params);
set(state => {
state.tasks = response.data;
state.loading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
}
},
createTask: async (taskData) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const response = await get().apiService.createTask(taskData);
const newTask = response.data;
set(state => {
state.tasks.unshift(newTask);
state.loading = false;
});
return newTask;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
throw error;
}
},
updateTask: async (id, updates) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const response = await get().apiService.updateTask(id, updates);
const updatedTask = response.data;
set(state => {
const index = state.tasks.findIndex(task => task.id === id);
if (index !== -1) {
state.tasks[index] = updatedTask;
}
if (state.selectedTask?.id === id) {
state.selectedTask = updatedTask;
}
state.loading = false;
});
return updatedTask;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
throw error;
}
},
deleteTask: async (id) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
await get().apiService.deleteTask(id);
set(state => {
state.tasks = state.tasks.filter(task => task.id !== id);
if (state.selectedTask?.id === id) {
state.selectedTask = null;
}
state.loading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
throw error;
}
},
duplicateTask: async (id) => {
const originalTask = get().tasks.find(task => task.id === id);
if (!originalTask) {
throw new Error('Task not found');
}
const duplicatedTaskData = {
...originalTask,
title: `${originalTask.title} (Copy)`,
status: TaskStatus.TODO,
completedAt: undefined,
progress: 0,
subtasks: originalTask.subtasks.map(subtask => ({
...subtask,
completed: false
}))
};
// 移除不需要的字段
delete (duplicatedTaskData as any).id;
delete (duplicatedTaskData as any).createdAt;
delete (duplicatedTaskData as any).updatedAt;
return get().createTask(duplicatedTaskData);
},
bulkUpdateTasks: async (ids, updates) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const response = await get().apiService.bulkUpdateTasks(ids, updates);
const updatedTasks = response.data;
set(state => {
updatedTasks.forEach(updatedTask => {
const index = state.tasks.findIndex(task => task.id === updatedTask.id);
if (index !== -1) {
state.tasks[index] = updatedTask;
}
});
state.loading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
throw error;
}
},
bulkDeleteTasks: async (ids) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
await Promise.all(ids.map(id => get().apiService.deleteTask(id)));
set(state => {
state.tasks = state.tasks.filter(task => !ids.includes(task.id));
if (state.selectedTask && ids.includes(state.selectedTask.id)) {
state.selectedTask = null;
}
state.loading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
throw error;
}
},
toggleTaskStatus: async (id) => {
const task = get().tasks.find(t => t.id === id);
if (!task) return;
const newStatus = task.status === TaskStatus.COMPLETED
? TaskStatus.TODO
: TaskStatus.COMPLETED;
const updates: Partial<Task> = {
status: newStatus,
completedAt: newStatus === TaskStatus.COMPLETED ? new Date() : undefined,
progress: newStatus === TaskStatus.COMPLETED ? 100 : task.progress
};
await get().updateTask(id, updates);
},
updateTaskProgress: async (id, progress) => {
const updates: Partial<Task> = {
progress,
status: progress === 100 ? TaskStatus.COMPLETED :
progress > 0 ? TaskStatus.IN_PROGRESS : TaskStatus.TODO,
completedAt: progress === 100 ? new Date() : undefined
};
await get().updateTask(id, updates);
},
// 子任务管理
addSubtask: async (taskId, subtaskData) => {
const task = get().tasks.find(t => t.id === taskId);
if (!task) return;
const newSubtask: Subtask = {
...subtaskData,
id: crypto.randomUUID(),
taskId,
createdAt: new Date(),
updatedAt: new Date(),
order: task.subtasks.length
};
const updatedSubtasks = [...task.subtasks, newSubtask];
await get().updateTask(taskId, { subtasks: updatedSubtasks });
},
updateSubtask: async (taskId, subtaskId, updates) => {
const task = get().tasks.find(t => t.id === taskId);
if (!task) return;
const updatedSubtasks = task.subtasks.map(subtask =>
subtask.id === subtaskId
? { ...subtask, ...updates, updatedAt: new Date() }
: subtask
);
await get().updateTask(taskId, { subtasks: updatedSubtasks });
},
deleteSubtask: async (taskId, subtaskId) => {
const task = get().tasks.find(t => t.id === taskId);
if (!task) return;
const updatedSubtasks = task.subtasks.filter(subtask => subtask.id !== subtaskId);
await get().updateTask(taskId, { subtasks: updatedSubtasks });
},
toggleSubtask: async (taskId, subtaskId) => {
const task = get().tasks.find(t => t.id === taskId);
if (!task) return;
const subtask = task.subtasks.find(s => s.id === subtaskId);
if (!subtask) return;
await get().updateSubtask(taskId, subtaskId, { completed: !subtask.completed });
// 更新主任务进度
const completedSubtasks = task.subtasks.filter(s =>
s.id === subtaskId ? !subtask.completed : s.completed
).length;
const totalSubtasks = task.subtasks.length;
const progress = totalSubtasks > 0 ? Math.round((completedSubtasks / totalSubtasks) * 100) : 0;
await get().updateTaskProgress(taskId, progress);
},
// 其他方法的实现...
fetchCategories: async () => {
// 实现分类获取逻辑
},
createCategory: async (categoryData) => {
// 实现分类创建逻辑
return {} as Category;
},
updateCategory: async (id, updates) => {
// 实现分类更新逻辑
return {} as Category;
},
deleteCategory: async (id) => {
// 实现分类删除逻辑
},
fetchTags: async () => {
// 实现标签获取逻辑
},
createTag: async (tagData) => {
// 实现标签创建逻辑
return {} as Tag;
},
updateTag: async (id, updates) => {
// 实现标签更新逻辑
return {} as Tag;
},
deleteTag: async (id) => {
// 实现标签删除逻辑
},
setFilters: (filters) => {
set(state => {
state.filters = { ...state.filters, ...filters };
});
},
clearFilters: () => {
set(state => {
state.filters = {};
});
},
searchTasks: async (query) => {
await get().fetchTasks({ ...get().filters, search: query });
},
fetchStatistics: async () => {
try {
const response = await get().apiService.fetchStatistics();
set(state => {
state.statistics = response.data;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
}
},
setSelectedTask: (task) => {
set(state => {
state.selectedTask = task;
});
},
setLoading: (loading) => {
set(state => {
state.loading = loading;
});
},
setError: (error) => {
set(state => {
state.error = error;
});
},
clearError: () => {
set(state => {
state.error = null;
});
}
})),
{
name: 'task-store',
partialize: (state) => ({
tasks: state.tasks,
categories: state.categories,
tags: state.tags,
filters: state.filters
})
}
),
{ name: 'task-store' }
)
);
// 自定义Hook用于任务操作
export const useTasks = () => {
const store = useTaskStore();
return {
// 状态
tasks: store.tasks,
loading: store.loading,
error: store.error,
selectedTask: store.selectedTask,
// 操作
fetchTasks: store.fetchTasks,
createTask: store.createTask,
updateTask: store.updateTask,
deleteTask: store.deleteTask,
toggleTaskStatus: store.toggleTaskStatus,
// 工具方法
getTaskById: (id: string) => store.tasks.find(task => task.id === id),
getTasksByStatus: (status: TaskStatus) => store.tasks.filter(task => task.status === status),
getOverdueTasks: () => store.tasks.filter(task =>
task.dueDate && new Date(task.dueDate) < new Date() && task.status !== TaskStatus.COMPLETED
),
getTasksByPriority: (priority: TaskPriority) => store.tasks.filter(task => task.priority === priority)
};
};任务CRUD操作的核心特点:
💼 实现价值:通过完整的CRUD操作实现,为用户提供流畅的任务管理体验,同时确保数据的一致性和可靠性。
// 🎉 分类和标签管理系统
// 分类管理服务
class CategoryService {
private baseUrl = '/api/categories';
async fetchCategories(): Promise<ApiResponse<Category[]>> {
const response = await fetch(this.baseUrl);
if (!response.ok) {
throw new Error(`Failed to fetch categories: ${response.statusText}`);
}
return response.json();
}
async createCategory(category: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>): Promise<ApiResponse<Category>> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(category),
});
if (!response.ok) {
throw new Error(`Failed to create category: ${response.statusText}`);
}
return response.json();
}
async updateCategory(id: string, updates: Partial<Category>): Promise<ApiResponse<Category>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
if (!response.ok) {
throw new Error(`Failed to update category: ${response.statusText}`);
}
return response.json();
}
async deleteCategory(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`Failed to delete category: ${response.statusText}`);
}
return response.json();
}
async reorderCategories(categoryIds: string[]): Promise<ApiResponse<Category[]>> {
const response = await fetch(`${this.baseUrl}/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ categoryIds }),
});
if (!response.ok) {
throw new Error(`Failed to reorder categories: ${response.statusText}`);
}
return response.json();
}
}
// 标签管理服务
class TagService {
private baseUrl = '/api/tags';
async fetchTags(): Promise<ApiResponse<Tag[]>> {
const response = await fetch(this.baseUrl);
if (!response.ok) {
throw new Error(`Failed to fetch tags: ${response.statusText}`);
}
return response.json();
}
async createTag(tag: Omit<Tag, 'id' | 'createdAt' | 'updatedAt' | 'usageCount'>): Promise<ApiResponse<Tag>> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(tag),
});
if (!response.ok) {
throw new Error(`Failed to create tag: ${response.statusText}`);
}
return response.json();
}
async updateTag(id: string, updates: Partial<Tag>): Promise<ApiResponse<Tag>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
if (!response.ok) {
throw new Error(`Failed to update tag: ${response.statusText}`);
}
return response.json();
}
async deleteTag(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`Failed to delete tag: ${response.statusText}`);
}
return response.json();
}
async searchTags(query: string): Promise<ApiResponse<Tag[]>> {
const response = await fetch(`${this.baseUrl}/search?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error(`Failed to search tags: ${response.statusText}`);
}
return response.json();
}
async getPopularTags(limit: number = 10): Promise<ApiResponse<Tag[]>> {
const response = await fetch(`${this.baseUrl}/popular?limit=${limit}`);
if (!response.ok) {
throw new Error(`Failed to fetch popular tags: ${response.statusText}`);
}
return response.json();
}
}
// 分类和标签状态管理
interface CategoryTagState {
categories: Category[];
tags: Tag[];
popularTags: Tag[];
loading: boolean;
error: string | null;
}
interface CategoryTagActions {
// 分类操作
fetchCategories: () => Promise<void>;
createCategory: (category: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>) => Promise<Category>;
updateCategory: (id: string, updates: Partial<Category>) => Promise<Category>;
deleteCategory: (id: string) => Promise<void>;
reorderCategories: (categoryIds: string[]) => Promise<void>;
// 标签操作
fetchTags: () => Promise<void>;
createTag: (tag: Omit<Tag, 'id' | 'createdAt' | 'updatedAt' | 'usageCount'>) => Promise<Tag>;
updateTag: (id: string, updates: Partial<Tag>) => Promise<Tag>;
deleteTag: (id: string) => Promise<void>;
searchTags: (query: string) => Promise<Tag[]>;
fetchPopularTags: () => Promise<void>;
// 工具方法
getCategoryById: (id: string) => Category | undefined;
getTagById: (id: string) => Tag | undefined;
getCategoryTree: () => CategoryTreeNode[];
getTagsByUsage: () => Tag[];
}
interface CategoryTreeNode extends Category {
children: CategoryTreeNode[];
level: number;
}
export const useCategoryTagStore = create<CategoryTagState & CategoryTagActions>()(
devtools(
persist(
immer((set, get) => ({
// 初始状态
categories: [],
tags: [],
popularTags: [],
loading: false,
error: null,
// 服务实例
categoryService: new CategoryService(),
tagService: new TagService(),
// 分类操作
fetchCategories: async () => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const response = await get().categoryService.fetchCategories();
set(state => {
state.categories = response.data;
state.loading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
}
},
createCategory: async (categoryData) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const response = await get().categoryService.createCategory(categoryData);
const newCategory = response.data;
set(state => {
state.categories.push(newCategory);
state.loading = false;
});
return newCategory;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
throw error;
}
},
updateCategory: async (id, updates) => {
try {
const response = await get().categoryService.updateCategory(id, updates);
const updatedCategory = response.data;
set(state => {
const index = state.categories.findIndex(cat => cat.id === id);
if (index !== -1) {
state.categories[index] = updatedCategory;
}
});
return updatedCategory;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
deleteCategory: async (id) => {
try {
await get().categoryService.deleteCategory(id);
set(state => {
state.categories = state.categories.filter(cat => cat.id !== id);
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
reorderCategories: async (categoryIds) => {
try {
const response = await get().categoryService.reorderCategories(categoryIds);
set(state => {
state.categories = response.data;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
// 标签操作
fetchTags: async () => {
try {
const response = await get().tagService.fetchTags();
set(state => {
state.tags = response.data;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
}
},
createTag: async (tagData) => {
try {
const response = await get().tagService.createTag(tagData);
const newTag = response.data;
set(state => {
state.tags.push(newTag);
});
return newTag;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
updateTag: async (id, updates) => {
try {
const response = await get().tagService.updateTag(id, updates);
const updatedTag = response.data;
set(state => {
const index = state.tags.findIndex(tag => tag.id === id);
if (index !== -1) {
state.tags[index] = updatedTag;
}
});
return updatedTag;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
deleteTag: async (id) => {
try {
await get().tagService.deleteTag(id);
set(state => {
state.tags = state.tags.filter(tag => tag.id !== id);
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
searchTags: async (query) => {
try {
const response = await get().tagService.searchTags(query);
return response.data;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
return [];
}
},
fetchPopularTags: async () => {
try {
const response = await get().tagService.getPopularTags();
set(state => {
state.popularTags = response.data;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
}
},
// 工具方法
getCategoryById: (id) => {
return get().categories.find(cat => cat.id === id);
},
getTagById: (id) => {
return get().tags.find(tag => tag.id === id);
},
getCategoryTree: () => {
const categories = get().categories;
const categoryMap = new Map<string, CategoryTreeNode>();
const rootCategories: CategoryTreeNode[] = [];
// 创建节点映射
categories.forEach(category => {
categoryMap.set(category.id, {
...category,
children: [],
level: 0
});
});
// 构建树结构
categories.forEach(category => {
const node = categoryMap.get(category.id)!;
if (category.parentId) {
const parent = categoryMap.get(category.parentId);
if (parent) {
node.level = parent.level + 1;
parent.children.push(node);
}
} else {
rootCategories.push(node);
}
});
// 按order排序
const sortByOrder = (nodes: CategoryTreeNode[]) => {
nodes.sort((a, b) => a.order - b.order);
nodes.forEach(node => sortByOrder(node.children));
};
sortByOrder(rootCategories);
return rootCategories;
},
getTagsByUsage: () => {
return [...get().tags].sort((a, b) => b.usageCount - a.usageCount);
}
})),
{
name: 'category-tag-store',
partialize: (state) => ({
categories: state.categories,
tags: state.tags,
popularTags: state.popularTags
})
}
),
{ name: 'category-tag-store' }
)
);
// 自定义Hook
export const useCategoriesAndTags = () => {
const store = useCategoryTagStore();
return {
// 状态
categories: store.categories,
tags: store.tags,
popularTags: store.popularTags,
loading: store.loading,
error: store.error,
// 操作
fetchCategories: store.fetchCategories,
createCategory: store.createCategory,
updateCategory: store.updateCategory,
deleteCategory: store.deleteCategory,
fetchTags: store.fetchTags,
createTag: store.createTag,
updateTag: store.updateTag,
deleteTag: store.deleteTag,
searchTags: store.searchTags,
// 工具方法
getCategoryTree: store.getCategoryTree,
getTagsByUsage: store.getTagsByUsage,
getCategoryById: store.getCategoryById,
getTagById: store.getTagById
};
};// 🎉 智能提醒系统
// 提醒服务
class ReminderService {
private baseUrl = '/api/reminders';
private notificationPermission: NotificationPermission = 'default';
constructor() {
this.requestNotificationPermission();
}
async requestNotificationPermission(): Promise<NotificationPermission> {
if ('Notification' in window) {
this.notificationPermission = await Notification.requestPermission();
}
return this.notificationPermission;
}
async createReminder(reminder: Omit<Reminder, 'id' | 'createdAt' | 'updatedAt'>): Promise<ApiResponse<Reminder>> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(reminder),
});
if (!response.ok) {
throw new Error(`Failed to create reminder: ${response.statusText}`);
}
return response.json();
}
async updateReminder(id: string, updates: Partial<Reminder>): Promise<ApiResponse<Reminder>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
if (!response.ok) {
throw new Error(`Failed to update reminder: ${response.statusText}`);
}
return response.json();
}
async deleteReminder(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`Failed to delete reminder: ${response.statusText}`);
}
return response.json();
}
// 显示浏览器通知
showNotification(title: string, options: NotificationOptions = {}): Notification | null {
if (this.notificationPermission === 'granted') {
return new Notification(title, {
icon: '/favicon.ico',
badge: '/badge.png',
...options
});
}
return null;
}
// 计算下次提醒时间
calculateNextReminderTime(reminder: Reminder): Date | null {
if (!reminder.repeatPattern) {
return null;
}
const { type, interval, daysOfWeek, endDate, maxOccurrences } = reminder.repeatPattern;
const now = new Date();
const triggerTime = new Date(reminder.triggerTime);
switch (type) {
case 'daily':
const nextDaily = new Date(triggerTime);
nextDaily.setDate(nextDaily.getDate() + interval);
return nextDaily > now ? nextDaily : null;
case 'weekly':
if (daysOfWeek && daysOfWeek.length > 0) {
const nextWeekly = new Date(triggerTime);
const currentDay = nextWeekly.getDay();
const nextDay = daysOfWeek.find(day => day > currentDay) || daysOfWeek[0];
if (nextDay > currentDay) {
nextWeekly.setDate(nextWeekly.getDate() + (nextDay - currentDay));
} else {
nextWeekly.setDate(nextWeekly.getDate() + (7 - currentDay + nextDay));
}
return nextWeekly > now ? nextWeekly : null;
}
break;
case 'monthly':
const nextMonthly = new Date(triggerTime);
nextMonthly.setMonth(nextMonthly.getMonth() + interval);
return nextMonthly > now ? nextMonthly : null;
case 'yearly':
const nextYearly = new Date(triggerTime);
nextYearly.setFullYear(nextYearly.getFullYear() + interval);
return nextYearly > now ? nextYearly : null;
}
return null;
}
// 检查并触发到期的提醒
checkAndTriggerReminders(reminders: Reminder[], tasks: Task[]): void {
const now = new Date();
reminders.forEach(reminder => {
if (!reminder.isActive || reminder.isSent) return;
const triggerTime = new Date(reminder.triggerTime);
if (triggerTime <= now) {
const task = tasks.find(t => t.id === reminder.taskId);
if (task) {
this.triggerReminder(reminder, task);
}
}
});
}
private triggerReminder(reminder: Reminder, task: Task): void {
const title = `任务提醒: ${task.title}`;
const message = reminder.message || `任务 "${task.title}" 需要您的关注`;
// 显示浏览器通知
const notification = this.showNotification(title, {
body: message,
tag: `reminder-${reminder.id}`,
requireInteraction: true,
actions: [
{ action: 'complete', title: '标记完成' },
{ action: 'snooze', title: '稍后提醒' }
]
});
if (notification) {
notification.onclick = () => {
window.focus();
// 导航到任务详情页
window.location.href = `/tasks/${task.id}`;
};
notification.onclose = () => {
// 标记提醒已发送
this.updateReminder(reminder.id, { isSent: true });
};
}
// 播放提醒声音(如果用户启用)
this.playReminderSound();
// 如果是重复提醒,计算下次提醒时间
if (reminder.repeatPattern) {
const nextTime = this.calculateNextReminderTime(reminder);
if (nextTime) {
this.createReminder({
...reminder,
triggerTime: nextTime,
isSent: false
});
}
}
}
private playReminderSound(): void {
try {
const audio = new Audio('/sounds/notification.mp3');
audio.volume = 0.5;
audio.play().catch(error => {
console.warn('Failed to play reminder sound:', error);
});
} catch (error) {
console.warn('Reminder sound not available:', error);
}
}
}
// 提醒状态管理
interface ReminderState {
reminders: Reminder[];
loading: boolean;
error: string | null;
notificationPermission: NotificationPermission;
}
interface ReminderActions {
fetchReminders: (taskId?: string) => Promise<void>;
createReminder: (reminder: Omit<Reminder, 'id' | 'createdAt' | 'updatedAt'>) => Promise<Reminder>;
updateReminder: (id: string, updates: Partial<Reminder>) => Promise<Reminder>;
deleteReminder: (id: string) => Promise<void>;
// 智能提醒功能
createSmartReminder: (taskId: string, type: 'before_due' | 'daily' | 'weekly') => Promise<Reminder>;
snoozeReminder: (id: string, minutes: number) => Promise<void>;
checkPendingReminders: () => void;
// 通知权限管理
requestNotificationPermission: () => Promise<NotificationPermission>;
// 工具方法
getRemindersByTask: (taskId: string) => Reminder[];
getActiveReminders: () => Reminder[];
getOverdueReminders: () => Reminder[];
}
export const useReminderStore = create<ReminderState & ReminderActions>()(
devtools(
immer((set, get) => ({
// 初始状态
reminders: [],
loading: false,
error: null,
notificationPermission: 'default',
// 服务实例
reminderService: new ReminderService(),
fetchReminders: async (taskId) => {
set(state => {
state.loading = true;
state.error = null;
});
try {
const url = taskId ? `/api/reminders?taskId=${taskId}` : '/api/reminders';
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch reminders: ${response.statusText}`);
}
const data = await response.json();
set(state => {
state.reminders = data.data;
state.loading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
}
},
createReminder: async (reminderData) => {
try {
const response = await get().reminderService.createReminder(reminderData);
const newReminder = response.data;
set(state => {
state.reminders.push(newReminder);
});
return newReminder;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
updateReminder: async (id, updates) => {
try {
const response = await get().reminderService.updateReminder(id, updates);
const updatedReminder = response.data;
set(state => {
const index = state.reminders.findIndex(r => r.id === id);
if (index !== -1) {
state.reminders[index] = updatedReminder;
}
});
return updatedReminder;
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
deleteReminder: async (id) => {
try {
await get().reminderService.deleteReminder(id);
set(state => {
state.reminders = state.reminders.filter(r => r.id !== id);
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Unknown error';
});
throw error;
}
},
createSmartReminder: async (taskId, type) => {
const task = useTaskStore.getState().tasks.find(t => t.id === taskId);
if (!task) {
throw new Error('Task not found');
}
let triggerTime: Date;
let repeatPattern: RepeatPattern | undefined;
switch (type) {
case 'before_due':
if (!task.dueDate) {
throw new Error('Task has no due date');
}
triggerTime = new Date(task.dueDate);
triggerTime.setHours(triggerTime.getHours() - 1); // 1小时前提醒
break;
case 'daily':
triggerTime = new Date();
triggerTime.setHours(9, 0, 0, 0); // 每天上午9点
triggerTime.setDate(triggerTime.getDate() + 1); // 从明天开始
repeatPattern = {
type: 'daily',
interval: 1
};
break;
case 'weekly':
triggerTime = new Date();
triggerTime.setHours(9, 0, 0, 0);
triggerTime.setDate(triggerTime.getDate() + (7 - triggerTime.getDay() + 1)); // 下周一
repeatPattern = {
type: 'weekly',
interval: 1,
daysOfWeek: [1] // 周一
};
break;
}
const reminderData: Omit<Reminder, 'id' | 'createdAt' | 'updatedAt'> = {
taskId,
type: repeatPattern ? ReminderType.RECURRING : ReminderType.ABSOLUTE,
triggerTime,
isActive: true,
isSent: false,
repeatPattern
};
return get().createReminder(reminderData);
},
snoozeReminder: async (id, minutes) => {
const reminder = get().reminders.find(r => r.id === id);
if (!reminder) return;
const newTriggerTime = new Date();
newTriggerTime.setMinutes(newTriggerTime.getMinutes() + minutes);
await get().updateReminder(id, {
triggerTime: newTriggerTime,
isSent: false
});
},
checkPendingReminders: () => {
const reminders = get().reminders;
const tasks = useTaskStore.getState().tasks;
get().reminderService.checkAndTriggerReminders(reminders, tasks);
},
requestNotificationPermission: async () => {
const permission = await get().reminderService.requestNotificationPermission();
set(state => {
state.notificationPermission = permission;
});
return permission;
},
getRemindersByTask: (taskId) => {
return get().reminders.filter(r => r.taskId === taskId);
},
getActiveReminders: () => {
return get().reminders.filter(r => r.isActive && !r.isSent);
},
getOverdueReminders: () => {
const now = new Date();
return get().reminders.filter(r =>
r.isActive &&
!r.isSent &&
new Date(r.triggerTime) <= now
);
}
})),
{ name: 'reminder-store' }
)
);
// 自定义Hook
export const useReminders = () => {
const store = useReminderStore();
// 定期检查提醒
React.useEffect(() => {
const interval = setInterval(() => {
store.checkPendingReminders();
}, 60000); // 每分钟检查一次
return () => clearInterval(interval);
}, [store.checkPendingReminders]);
return {
reminders: store.reminders,
loading: store.loading,
error: store.error,
notificationPermission: store.notificationPermission,
fetchReminders: store.fetchReminders,
createReminder: store.createReminder,
updateReminder: store.updateReminder,
deleteReminder: store.deleteReminder,
createSmartReminder: store.createSmartReminder,
snoozeReminder: store.snoozeReminder,
requestNotificationPermission: store.requestNotificationPermission,
getRemindersByTask: store.getRemindersByTask,
getActiveReminders: store.getActiveReminders,
getOverdueReminders: store.getOverdueReminders
};
};分类标签和提醒系统的核心特点:
💼 功能价值:通过完善的分类标签系统和智能提醒功能,帮助用户更好地组织和管理任务,提升工作效率和时间管理能力。
通过本节JavaScript核心功能实现的学习,你已经掌握:
A: 使用不可变数据结构,合理设计状态结构避免深层嵌套,使用选择器模式优化数据访问,实现乐观更新提升用户体验,同时保持数据的一致性和可预测性。
A: 采用扁平化的标签设计避免复杂的层级关系,使用索引优化查询性能,实现标签的懒加载和缓存机制,同时提供标签使用统计帮助用户管理标签。
A: 统一使用UTC时间存储,在显示时转换为用户本地时区,支持用户设置首选时区,提醒时间计算考虑夏令时变化,提供多语言的提醒消息模板。
A: 实现虚拟滚动处理大列表,使用分页和懒加载减少初始加载时间,实现智能缓存策略,使用Web Worker处理复杂计算,优化数据库查询和索引。
A: 使用Service Worker实现离线缓存,设计冲突解决策略处理数据冲突,实现增量同步减少数据传输,提供同步状态指示器,支持手动触发同步操作。
// 良好的状态结构设计
interface AppState {
// 按功能模块组织
tasks: {
items: Task[];
loading: boolean;
error: string | null;
filters: TaskFilters;
pagination: PaginationInfo;
};
// 规范化数据结构
categories: {
byId: Record<string, Category>;
allIds: string[];
tree: CategoryTreeNode[];
};
// UI状态分离
ui: {
selectedTaskId: string | null;
sidebarOpen: boolean;
activeView: 'list' | 'board' | 'calendar';
};
}// 统一的错误处理
class ErrorHandler {
static handle(error: unknown, context: string): string {
console.error(`Error in ${context}:`, error);
if (error instanceof Error) {
// 网络错误
if (error.message.includes('fetch')) {
return '网络连接失败,请检查网络设置';
}
// 权限错误
if (error.message.includes('401')) {
return '登录已过期,请重新登录';
}
// 验证错误
if (error.message.includes('validation')) {
return '输入数据格式不正确,请检查后重试';
}
return error.message;
}
return '发生未知错误,请稍后重试';
}
static async withErrorHandling<T>(
operation: () => Promise<T>,
context: string
): Promise<T> {
try {
return await operation();
} catch (error) {
const message = this.handle(error, context);
throw new Error(message);
}
}
}// 使用React.memo优化组件渲染
const TaskItem = React.memo(({ task, onUpdate }: TaskItemProps) => {
// 使用useCallback避免不必要的重新渲染
const handleToggle = useCallback(() => {
onUpdate(task.id, { status: task.status === 'completed' ? 'todo' : 'completed' });
}, [task.id, task.status, onUpdate]);
return (
<div className="task-item">
<input
type="checkbox"
checked={task.status === 'completed'}
onChange={handleToggle}
/>
<span>{task.title}</span>
</div>
);
});
// 使用useMemo优化计算
const TaskList = ({ tasks, filters }: TaskListProps) => {
const filteredTasks = useMemo(() => {
return tasks.filter(task => {
if (filters.status && task.status !== filters.status) return false;
if (filters.category && task.categoryId !== filters.category) return false;
if (filters.search && !task.title.toLowerCase().includes(filters.search.toLowerCase())) return false;
return true;
});
}, [tasks, filters]);
return (
<div>
{filteredTasks.map(task => (
<TaskItem key={task.id} task={task} onUpdate={updateTask} />
))}
</div>
);
};// 乐观更新实现
const useOptimisticUpdate = () => {
const updateTaskOptimistically = async (id: string, updates: Partial<Task>) => {
// 立即更新UI
const originalTask = getTaskById(id);
updateTaskInStore(id, updates);
try {
// 发送API请求
await updateTaskAPI(id, updates);
} catch (error) {
// 失败时回滚
if (originalTask) {
updateTaskInStore(id, originalTask);
}
throw error;
}
};
return { updateTaskOptimistically };
};// 细粒度的加载状态
interface LoadingState {
global: boolean;
tasks: {
fetching: boolean;
creating: boolean;
updating: Record<string, boolean>;
deleting: Record<string, boolean>;
};
categories: {
fetching: boolean;
creating: boolean;
};
}
// 使用加载状态
const TaskItem = ({ task }: { task: Task }) => {
const { loading } = useTaskStore();
const isUpdating = loading.tasks.updating[task.id];
return (
<div className={`task-item ${isUpdating ? 'updating' : ''}`}>
{isUpdating && <Spinner size="small" />}
{/* 任务内容 */}
</div>
);
};"核心功能的实现质量直接决定了整个应用的用户体验。通过合理的架构设计、完善的错误处理、优化的性能表现,我们可以构建出既强大又易用的任务管理系统。记住,好的代码不仅要功能完整,更要易于维护和扩展。"