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' const createTodoSchema = z.object({ title: z.string().min(1, '标题不能为空'), }) const updateTodoSchema = z.object({ id: z.uuid(), completed: z.boolean(), }) const deleteTodoSchema = z.object({ id: z.uuid(), }) // Server Functions - CRUD 操作 const getTodos = createServerFn({ method: 'GET' }).handler(async () => { 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 [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 return (
{/* Header */}

我的待办事项

已完成 {completedCount} / {totalCount} 项任务

{/* Add Todo Form */}
{/* Progress Bar */}
完成进度 {totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0} %
0 ? (completedCount / totalCount) * 100 : 0}%`, }} />
{/* Todo List */}
{todos.length === 0 ? (

暂无待办事项

添加你的第一个任务开始吧!

) : ( todos.map((todo) => (
{/* Checkbox */} {/* Todo Content */}

{todo.title}

{new Date(todo.createdAt).toLocaleDateString('zh-CN')}
{/* Status Badge and Delete Button */}
{todo.completed ? '已完成' : '进行中'}
)) )}
{/* Footer Stats */} {todos.length > 0 && (

{totalCount - completedCount}

待完成

{completedCount}

已完成

)}
) }