VUE+Supabase

一、前期准备

1. 注册 Supabase 账号并创建项目

  1. 访问 Supabase 官网 注册账号(支持 GitHub / 邮箱登录)。
  2. 登录后点击 New Project,填写项目信息:
    • Project name:项目名称(如 vue-supabase-demo
    • Database password:数据库密码(记住!后续连接需要)
    • Region:区域(选离你最近的,如 Asia Pacific (Singapore)
  3. 点击 Create Project,等待约 2 分钟项目初始化完成。

2. 获取 Supabase 项目密钥(关键!)

项目创建完成后,进入项目控制台 → 点击左侧 Settings → API,复制两个核心信息:

  • Project URL:项目接口地址(如 https://abc123.supabase.co
  • anon public:匿名访问密钥(如 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

这两个信息用于前端连接 Supabase,后续会用到。

二、搭建 Vue 项目并集成 Supabase

1. 创建 Vue 3 项目(若已有项目可跳过)

打开终端,执行以下命令创建 Vite + Vue 项目:

# 创建项目

npm create vite@latest vue-supabase-demo -- --template vue

# 进入项目目录

cd vue-supabase-demo

# 安装依赖

npm install

2. 安装 Supabase Vue 客户端

Supabase 官方提供了 Vue 专属的客户端库 @supabase/supabase-js(Vue 3 兼容),安装命令:

npm install @supabase/supabase-js

3. 初始化 Supabase 客户端

为了方便全局使用,我们创建一个 Supabase 实例文件:

  1. 在 src 目录下创建 utils/supabase.js 文件;
  2. 粘贴以下代码(替换为你自己的 Project URL 和 anon public 密钥):
// src/utils/supabase.js
import { createClient } from '@supabase/supabase-js'

// 你的 Supabase 项目配置(替换成自己的!)
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

// 创建 Supabase 客户端实例
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
  1. 在项目根目录创建 .env 文件,存储环境变量(避免密钥硬编码):
# .env
VITE_SUPABASE_URL=你的 Project URL
VITE_SUPABASE_ANON_KEY=你的 anon public 密钥

4. 验证连接

在 src/App.vue 中引入 supabase 实例,测试是否连接成功:

<!-- src/App.vue -->
<script setup>
import { supabase } from './utils/supabase'

// 测试连接
const testConnection = async () => {
  const { data, error } = await supabase.from('test').select('*').limit(1)
  if (error) {
    console.log('连接成功(测试表不存在属于正常):', error.message)
  } else {
    console.log('连接成功,数据:', data)
  }
}

// 组件挂载时执行测试
testConnection()
</script>

<template>
  <h1>Vue + Supabase 测试</h1>
</template>

运行项目 npm run dev,打开浏览器控制台,若显示 连接成功 则说明集成完成!

三、核心功能实现(实战)

下面我们实现 3 个最常用的功能:用户认证(邮箱登录)、数据库 CRUD、文件存储,全程结合 Vue 3 的 Composition API。

1. 用户认证(邮箱 / 密码登录)

Supabase 内置了完整的用户认证系统,支持邮箱、OAuth(GitHub/Google)等,无需自己写后端逻辑。

步骤 1:启用 Supabase 认证功能
  1. 进入 Supabase 控制台 → 左侧 Authentication → Providers
  2. 启用 Email 提供商(默认已启用),无需额外配置。
步骤 2:Vue 前端实现登录 / 注册 / 退出

创建 src/views/Auth.vue,实现完整的认证流程:

<!-- src/views/Auth.vue -->
<script setup>
import { ref } from 'vue'
import { supabase } from '@/utils/supabase'
import { useRouter } from 'vue-router'

const router = useRouter()

// 表单数据
const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)

// 注册用户
const signUp = async () => {
  try {
    loading.value = true
    error.value = ''
    // 调用 Supabase 注册接口(自动发送验证邮件)
    const { data, error: supabaseError } = await supabase.auth.signUp({
      email: email.value,
      password: password.value,
    })
    if (supabaseError) throw supabaseError
    alert('注册成功!请查收验证邮件~')
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 登录用户
const signIn = async () => {
  try {
    loading.value = true
    error.value = ''
    // 调用 Supabase 登录接口
    const { data, error: supabaseError } = await supabase.auth.signInWithPassword({
      email: email.value,
      password: password.value,
    })
    if (supabaseError) throw supabaseError
    alert('登录成功!')
    router.push('/dashboard') // 登录成功跳转到仪表盘
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 退出登录
const signOut = async () => {
  await supabase.auth.signOut()
  alert('退出成功!')
  router.push('/')
}
</script>

<template>
  <div class="auth-container">
    <h2>Vue + Supabase 认证</h2>
    <div class="form-group">
      <label>邮箱</label>
      <input
        type="email"
        v-model="email"
        placeholder="请输入邮箱"
        required
      />
    </div>
    <div class="form-group">
      <label>密码</label>
      <input
        type="password"
        v-model="password"
        placeholder="请输入密码(至少6位)"
        required
      />
    </div>
    <div class="error" v-if="error">{{ error }}</div>
    <button @click="signUp" :disabled="loading">注册</button>
    <button @click="signIn" :disabled="loading" style="margin-left: 10px;">登录</button>
    <button @click="signOut" style="margin-left: 10px;">退出</button>
  </div>
</template>

<style scoped>
.auth-container {
  max-width: 400px;
  margin: 50px auto;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
}
.form-group {
  margin-bottom: 15px;
}
input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
button {
  padding: 8px 16px;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
button:disabled {
  background: #94a3b8;
  cursor: not-allowed;
}
.error {
  color: #ef4444;
  margin: 10px 0;
}
</style>
步骤 3:监听用户登录状态(全局)

在 src/App.vue 中监听用户认证状态,实现 “登录后显示仪表盘,未登录显示登录页”:

<!-- src/App.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from './utils/supabase'
import Auth from './views/Auth.vue'
import Dashboard from './views/Dashboard.vue'

const user = ref(null)

// 监听用户状态变化(登录/退出时触发)
onMounted(() => {
  // 获取当前登录用户
  user.value = supabase.auth.currentUser

  // 监听认证状态变化
  supabase.auth.onAuthStateChange((_event, session) => {
    user.value = session?.user || null
  })
})
</script>

<template>
  <div id="app">
    <h1>Vue + Supabase 全栈Demo</h1>
    <!-- 未登录显示登录页,已登录显示仪表盘 -->
    <Auth v-if="!user" />
    <Dashboard v-else />
  </div>
</template>

2. 数据库 CRUD(核心功能)

Supabase 内置 PostgreSQL 数据库,支持通过前端直接操作(无需后端接口),下面实现 “用户列表” 的增删改查。

步骤 1:创建数据库表(Supabase 控制台)
  1. 进入 Supabase 控制台 → 左侧 Database → Tables → Create a new table
  2. 创建 users 表(自定义表名),添加以下字段:
    • id:主键(默认已创建,类型 uuid,自增);
    • name:字符串(必填,用户姓名);
    • age:整数(可选,用户年龄);
    • created_at:时间戳(默认已创建,记录创建时间);
  3. 点击 Save 保存表。
步骤 2:Vue 前端实现 CRUD(Dashboard.vue)

创建 src/views/Dashboard.vue,实现用户列表的增删改查:

<!-- src/views/Dashboard.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from '@/utils/supabase'

// 数据状态
const users = ref([])
const newUser = ref({ name: '', age: '' }) // 新增用户表单
const editingUser = ref(null) // 编辑中的用户
const loading = ref(false)
const error = ref('')

// 1. 查询所有用户(读取)
const fetchUsers = async () => {
  try {
    loading.value = true
    error.value = ''
    // 调用 Supabase 查询接口:from(表名).select(字段)
    const { data, error: supabaseError } = await supabase
      .from('users')
      .select('*') // 查询所有字段
      .order('created_at', { ascending: false }) // 按创建时间倒序

    if (supabaseError) throw supabaseError
    users.value = data
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 2. 新增用户(创建)
const addUser = async () => {
  if (!newUser.value.name) {
    error.value = '用户名不能为空!'
    return
  }
  try {
    loading.value = true
    error.value = ''
    // 调用 Supabase 插入接口:from(表名).insert(数据)
    const { data, error: supabaseError } = await supabase
      .from('users')
      .insert([newUser.value]) // 插入一条数据(数组形式支持批量插入)
      .select() // 插入后返回新增数据

    if (supabaseError) throw supabaseError
    users.value.unshift(data[0]) // 新增数据添加到列表头部
    newUser.value = { name: '', age: '' } // 重置表单
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 3. 编辑用户(更新)
const updateUser = async () => {
  if (!editingUser.value.name) {
    error.value = '用户名不能为空!'
    return
  }
  try {
    loading.value = true
    error.value = ''
    // 调用 Supabase 更新接口:from(表名).update(数据).eq(条件)
    const { error: supabaseError } = await supabase
      .from('users')
      .update({ name: editingUser.value.name, age: editingUser.value.age })
      .eq('id', editingUser.value.id) // 按 id 匹配要更新的记录

    if (supabaseError) throw supabaseError
    // 更新列表中的数据
    const index = users.value.findIndex(u => u.id === editingUser.value.id)
    users.value[index] = { ...editingUser.value }
    editingUser.value = null // 退出编辑状态
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 4. 删除用户(删除)
const deleteUser = async (userId) => {
  if (!confirm('确定要删除吗?')) return
  try {
    loading.value = true
    error.value = ''
    // 调用 Supabase 删除接口:from(表名).delete().eq(条件)
    const { error: supabaseError } = await supabase
      .from('users')
      .delete()
      .eq('id', userId) // 按 id 匹配要删除的记录

    if (supabaseError) throw supabaseError
    // 从列表中移除数据
    users.value = users.value.filter(u => u.id !== userId)
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 组件挂载时查询所有用户
onMounted(() => {
  fetchUsers()
})
</script>

<template>
  <div class="dashboard">
    <h2>用户管理仪表盘(已登录:{{ user.email }})</h2>
    <button @click="supabase.auth.signOut()">退出登录</button>

    <!-- 新增用户表单 -->
    <div class="add-user">
      <h3>新增用户</h3>
      <input
        type="text"
        v-model="newUser.name"
        placeholder="输入姓名"
        :disabled="loading"
      />
      <input
        type="number"
        v-model="newUser.age"
        placeholder="输入年龄"
        :disabled="loading"
      />
      <button @click="addUser" :disabled="loading">添加</button>
    </div>

    <!-- 错误提示 -->
    <div class="error" v-if="error">{{ error }}</div>

    <!-- 用户列表 -->
    <div class="user-list" v-if="!loading">
      <h3>用户列表(共 {{ users.length }} 人)</h3>
      <table border="1" cellpadding="8" cellspacing="0">
        <thead>
          <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>创建时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="u in users" :key="u.id">
            <td>{{ u.id }}</td>
            <td>
              <input
                v-if="editingUser?.id === u.id"
                v-model="editingUser.name"
                type="text"
              />
              <span v-else>{{ u.name }}</span>
            </td>
            <td>
              <input
                v-if="editingUser?.id === u.id"
                v-model="editingUser.age"
                type="number"
              />
              <span v-else>{{ u.age || '无' }}</span>
            </td>
            <td>{{ new Date(u.created_at).toLocaleString() }}</td>
            <td>
              <button
                @click="editingUser = u"
                v-if="editingUser?.id !== u.id"
                :disabled="loading"
              >
                编辑
              </button>
              <button
                @click="updateUser()"
                v-if="editingUser?.id === u.id"
                :disabled="loading"
              >
                保存
              </button>
              <button
                @click="deleteUser(u.id)"
                style="background: #ef4444; margin-left: 5px;"
                :disabled="loading"
              >
                删除
              </button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <div v-else>加载中...</div>
  </div>
</template>

<style scoped>
.dashboard {
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
}
.add-user {
  margin: 20px 0;
  display: flex;
  gap: 10px;
  align-items: center;
}
.add-user input {
  padding: 8px;
  flex: 1;
  max-width: 200px;
}
.user-list table {
  width: 100%;
  border-collapse: collapse;
}
.user-list th, .user-list td {
  text-align: left;
}
button {
  padding: 6px 12px;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
button:disabled {
  background: #94a3b8;
  cursor: not-allowed;
}
.error {
  color: #ef4444;
  margin: 10px 0;
}
</style>

3. 文件存储(可选,如上传头像)

Supabase 提供文件存储服务,支持上传图片、文档等文件,下面实现 “上传用户头像” 功能。

步骤 1:创建存储桶(Supabase 控制台)
  1. 进入 Supabase 控制台 → 左侧 Storage → Create a bucket
  2. 桶名称:avatars(存储用户头像);
  3. 权限:勾选 Public(公开访问,方便前端展示头像);
  4. 点击 Create 保存。
步骤 2:Vue 前端实现文件上传

在 Dashboard.vue 中添加头像上传功能(修改部分代码):

<!-- Dashboard.vue 新增/修改以下代码 -->
<script setup>
// 新增:文件上传相关
const avatarFile = ref(null) // 选中的文件
const avatarUrl = ref('') // 上传后的文件URL

// 上传头像
const uploadAvatar = async () => {
  if (!avatarFile.value) {
    error.value = '请选择文件!'
    return
  }
  try {
    loading.value = true
    error.value = ''
    // 文件名:使用当前时间戳 + 原文件名(避免重复)
    const fileName = `${Date.now()}-${avatarFile.value.name}`
    // 调用 Supabase 上传接口:storage.from(桶名).upload(文件路径, 文件)
    const { data, error: supabaseError } = await supabase.storage
      .from('avatars') // 桶名
      .upload(`public/${fileName}`, avatarFile.value, {
        cacheControl: '3600', // 缓存1小时
        upsert: false // 不覆盖已存在的文件
      })

    if (supabaseError) throw supabaseError
    // 获取上传后的文件公共URL
    const { data: urlData } = supabase.storage
      .from('avatars')
      .getPublicUrl(data.path) // data.path 是文件在桶中的路径

    avatarUrl.value = urlData.publicUrl
    alert('头像上传成功!')
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}
</script>

<template>
  <!-- 新增:头像上传区域 -->
  <div class="upload-avatar">
    <h3>上传头像</h3>
    <input type="file" accept="image/*" @change="avatarFile = $event.target.files[0]" />
    <button @click="uploadAvatar" :disabled="loading">上传</button>
    <div v-if="avatarUrl" style="margin-top: 10px;">
      <img :src="avatarUrl" alt="头像" style="width: 100px; height: 100px; object-fit: cover;" />
    </div>
  </div>
</template>

<style scoped>
/* 新增:上传区域样式 */
.upload-avatar {
  margin: 20px 0;
  padding: 10px;
  border: 1px dashed #ddd;
}
</style>

四、关键注意事项(避坑指南)

  1. 权限控制

    • 匿名用户默认没有数据库 / 存储的操作权限!如果需要匿名用户操作,需在 Supabase 控制台 → Authentication → Policies 中添加权限策略(如允许匿名用户查询 users 表)。
    • 生产环境建议使用 “已登录用户” 权限(通过 auth.uid() 验证用户身份)。
  2. 环境变量

    • Vue 3 + Vite 中,环境变量必须以 VITE_ 开头,否则无法在前端访问。
    • 不要将 service_role 密钥(管理员密钥)暴露在前端!前端只能用 anon public 密钥。
  3. 错误处理

    • 所有 Supabase 接口都是异步的,必须用 try/catch 捕获错误(如网络错误、权限不足、数据格式错误)。
  4. 数据验证

    • 前端需做基础数据验证(如必填字段、数据类型),后端可在 Supabase 控制台设置数据库约束(如非空、字段长度)。

五、扩展学习(后续可探索)

  1. OAuth 登录:Supabase 支持 GitHub、Google、Facebook 等 OAuth 登录,只需在控制台启用对应提供商,前端调用 supabase.auth.signInWithOAuth({ provider: 'github' }) 即可。
  2. 实时数据同步:Supabase 支持实时订阅数据库变化,通过 supabase.from('users').on('INSERT', (payload) => { ... }).subscribe() 实现 “别人新增用户,你的页面实时更新”。
  3. 行级安全策略(RLS):生产环境核心!通过 RLS 控制用户只能操作自己的数据(如用户只能修改自己创建的记录)。
  4. Supabase 函数:类似云函数,可在 Supabase 中编写 PostgreSQL 函数,实现复杂业务逻辑(如数据计算、第三方接口调用)。

总结

Vue + Supabase 是一套 “快速开发全栈应用” 的组合,核心优势是:

  • 前端用 Vue 3 写界面,简单高效;
  • 后端无需写代码,Supabase 提供数据库、认证、存储等一站式服务;
  • 开发速度快,适合原型开发、小型应用、个人项目。

按照上面的步骤,你已经实现了一个完整的全栈应用(认证 + 数据库 CRUD + 文件存储),可以在此基础上扩展更多功能!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值