From 928a78a33552ba86cb6db9cd9ff3808a961cb3e2 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sat, 17 Jan 2026 03:09:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加任务创建、完成状态切换和删除功能,优化界面交互并集成表单验证与加载状态反馈。 - 添加DOM和DOM.Iterable库支持以增强类型定义和浏览器API兼容性。 --- src/routes/todo.tsx | 159 ++++++++++++++++++++++++++++++++++++++++++-- tsconfig.json | 2 +- 2 files changed, 153 insertions(+), 8 deletions(-) diff --git a/src/routes/todo.tsx b/src/routes/todo.tsx index cc4531f..69cc967 100644 --- a/src/routes/todo.tsx +++ b/src/routes/todo.tsx @@ -1,23 +1,118 @@ -import { useSuspenseQuery } from '@tanstack/react-query' +import { useMutation, useSuspenseQuery } from '@tanstack/react-query' import { createFileRoute } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' +import { eq } from 'drizzle-orm' +import type { ChangeEvent, FormEvent } from 'react' +import { useState } from 'react' +import { z } from 'zod' import { db } from '@/db' +import { todoTable } from '@/db/schema' +// Zod Schemas +const createTodoSchema = z.object({ + title: z.string().min(1, '标题不能为空'), +}) + +const updateTodoSchema = z.object({ + id: z.string().uuid(), + completed: z.boolean(), +}) + +const deleteTodoSchema = z.object({ + id: z.string().uuid(), +}) + +// Server Functions - CRUD 操作 const getTodos = createServerFn({ method: 'GET' }).handler(async () => { - const todos = await db.query.todoTable.findMany() + const todos = await db.query.todoTable.findMany({ + orderBy: (todos, { desc }) => [desc(todos.createdAt)], + }) return todos }) +const createTodo = createServerFn({ method: 'POST' }) + .inputValidator(createTodoSchema) + .handler(async ({ data }) => { + const [newTodo] = await db + .insert(todoTable) + .values({ title: data.title }) + .returning() + return newTodo + }) + +const updateTodo = createServerFn({ method: 'POST' }) + .inputValidator(updateTodoSchema) + .handler(async ({ data }) => { + const [updatedTodo] = await db + .update(todoTable) + .set({ completed: data.completed }) + .where(eq(todoTable.id, data.id)) + .returning() + return updatedTodo + }) + +const deleteTodo = createServerFn({ method: 'POST' }) + .inputValidator(deleteTodoSchema) + .handler(async ({ data }) => { + await db.delete(todoTable).where(eq(todoTable.id, data.id)) + return { success: true } + }) + export const Route = createFileRoute('/todo')({ component: Todo, }) function Todo() { - const { data: todos } = useSuspenseQuery({ + const [newTodoTitle, setNewTodoTitle] = useState('') + const { data: todos, refetch } = useSuspenseQuery({ queryKey: ['todos'], queryFn: () => getTodos(), }) + // Mutations + const createMutation = useMutation({ + mutationFn: (title: string) => createTodo({ data: { title } }), + onSuccess: () => { + setNewTodoTitle('') + refetch() + }, + }) + + const updateMutation = useMutation({ + mutationFn: ({ id, completed }: { id: string; completed: boolean }) => + updateTodo({ data: { id, completed } }), + onSuccess: () => { + refetch() + }, + }) + + const deleteMutation = useMutation({ + mutationFn: (id: string) => deleteTodo({ data: { id } }), + onSuccess: () => { + refetch() + }, + }) + + // Handlers + const handleCreateTodo = (e: FormEvent) => { + e.preventDefault() + if (newTodoTitle.trim()) { + createMutation.mutate(newTodoTitle.trim()) + } + } + + const handleInputChange = (e: ChangeEvent) => { + setNewTodoTitle(e.target.value) + } + + const handleToggleTodo = (id: string, currentCompleted: boolean) => { + updateMutation.mutate({ id, completed: !currentCompleted }) + } + + const handleDeleteTodo = (id: string) => { + deleteMutation.mutate(id) + } + const completedCount = todos.filter((todo) => todo.completed).length const totalCount = todos.length @@ -34,6 +129,29 @@ function Todo() {

+ {/* Add Todo Form */} +
+
+
+ + +
+
+
+ {/* Progress Bar */}
@@ -92,7 +210,12 @@ function Todo() { >
{/* Checkbox */} -
+
+ {/* Todo Content */}
@@ -151,8 +274,8 @@ function Todo() {
- {/* Status Badge */} -
+ {/* Status Badge and Delete Button */} +
{todo.completed ? '已完成' : '进行中'} +
diff --git a/tsconfig.json b/tsconfig.json index dd311f4..2f120d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { // Environment setup & latest features - "lib": ["ESNext"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "target": "ESNext", "module": "Preserve", "moduleDetection": "force",