示例功能

以待办与个人资料作为可替换的功能范式。

有两个示例功能端到端接线出厂,让你可以复制范式而无需从头发明:一个 待办 (Todos)CRUD 列表,以及一个 个人资料 + 头像 编辑器。二者都标记为 Example ——是意在被你自己的产品数据替换的可用演示。两者都需要 Supabase 配置中的 Supabase 表。

待办(Example)

待办是完整数据路径的范例——service → TanStack Query → 组件。service (app/services/todos.ts)是一组基于类型化 Supabase 客户端的纯函数,不导入任何 组件:

// app/services/todos.ts
export const listTodos = async (): Promise<Todo[]> => {
  const { data, error } = await supabase
    .from('todos')
    .select(TODO_COLUMNS)
    .order('sort_order', { ascending: true })
    .order('created_at', { ascending: false })
  if (error) throw new Error(error.message)
  return (data ?? []) as Todo[]
}
// createTodo、toggleTodo、deleteTodo …

TodosDemoapp/routes/showcase/demos/TodosDemo.tsx)通过 TanStack Query 消费 这些 service——用 useQuery 取列表,用 useMutation 做创建/切换/删除——并在 UI 层负责缓存失效。RLS 保证用户只能看到自己的行(参见 数据层),因此 service 在读取时无需按用户 id 过滤。

service 就是替换点。要用你自己的 API 而非 Supabase 支撑待办,只需重写 app/services/todos.ts,而查询钩子与组件保持不动。

个人资料 + 头像(Example)

账户页(/accountAccountPage.tsx)编辑已登录用户的个人资料。有两个 service 支撑它:

  • app/services/profile.ts —— 基于 profiles 表的 getProfile / updateProfilefull_namebiophonelocationavatar_url)。
  • app/services/avatar.ts —— 针对 avatars 存储桶的头像上传/读取循环。

头像流程本身就是一个有用的范式:

  1. 校验 选中的图片(客户端)——guardAvatarInput 拒绝空文件、超过 5 MB 的 文件,或 PNG/JPEG/WebP 之外的 MIME 类型。
  2. 上传avatars/<userId>/avatar.<ext>upsert: true),随后把该路径 存到个人资料行上。
  3. 读回 经由 getAvatarUrl,它生成一个签名 URL(7 天 TTL),并追加一个 来自 updated_at 的破缓存键,使重新上传能立即显示。

该桶是私有的;读取走签名 URL 而非公开链接。请按 Supabase 配置所示配置桶及其存储策略。

刻意不包含的内容

这是一个桌面外壳,而非 SaaS UI 套件。它刻意不含:

  • 带图表的仪表盘,
  • 管理后台或用户管理表格,
  • CRM/内容管理脚手架。

这些示例存在的意义是展示一个功能的形态——一个类型化 service、查询钩子、一个 页面、RLS——而不是充当组件库。要找构建块(按钮、对话框、表单、表格以及其余 shadcn/ui 组件),请打开 /showcase/components 处的组件画廊。参见 能力展示

复制这个范式

要端到端添加你自己的 CRUD 功能,照着待办做:

SQL + RLS

创建带所有者列与"仅所有者"RLS 策略的表(参见 数据层)。

类型

重新生成或手动编辑 app/lib/supabase/database.types.ts

service

添加 app/services/<feature>.ts,作为基于类型化客户端的纯函数。

查询钩子 + 页面

useQuery/useMutation 包装 service,并在一个路由中渲染它(参见 界面与页面)。

相关页面

On this page