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