惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
Hacker News - Newest:
Hacker News - Newest: "LLM"
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
Last Week in AI
Last Week in AI
爱范儿
爱范儿
美团技术团队
V
Visual Studio Blog
P
Proofpoint News Feed
GbyAI
GbyAI
Y
Y Combinator Blog
博客园 - 司徒正美
IT之家
IT之家
Google DeepMind News
Google DeepMind News
F
Full Disclosure
aimingoo的专栏
aimingoo的专栏
宝玉的分享
宝玉的分享
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园_首页
M
MIT News - Artificial intelligence
V
V2EX
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
B
Blog
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
SecWiki News
SecWiki News
I
Intezer
P
Palo Alto Networks Blog
S
Security Affairs
L
LangChain Blog
C
Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Martin Fowler
Martin Fowler
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
H
Heimdal Security Blog
有赞技术团队
有赞技术团队
量子位
D
Docker
S
Secure Thoughts
N
News | PayPal Newsroom
The Last Watchdog
The Last Watchdog
H
Hacker News: Front Page
H
Hackread – Cybersecurity News, Data Breaches, AI and More

东东's Blog

Memos: MacOS 下编译安装 Aseprite 脚本 日本关西系列|Day 2 京都的半日闲逛 美国Apple官网购买礼品卡订阅 ChatGPT Plus 北京・中关村森林公园(2026) 日本关西系列|Day 1 抵达临空城与大阪首日 Memos: 查询 Apple ID 注册时间 Memos: Ghostty 开箱即用配置 代码考古:用 gitcharts 挖掘 Git 仓库的演变轨迹 烹饪日记:香煎罗非鱼 Memos: 新时代程序员的顶级焦虑 Memos: 博客新增图文布局和轮播图效果支持 Memos: 最适合空气炸锅烤着吃的红薯品类 Memos: 记录「95分」好吃的‘小帅香菇面’ 妻子爷爷的‘朝鲜军功奖章’ Memos: 博客切换为 Shiki 代码高亮方案 Memos: 博客新增划线、重点及荧光笔效果支持 记录博客字体分包与字体子集化 Memos: 邻座吃饭的一家三口 人在囧途之哈囧 哈尔滨・乡村的冬季 查看香烟生产日期 哈尔滨・东北虎林园 Memos: 来自日本的 ndjp 提供免费的三级子域名 Memos: 刚听说 autojump, 真的好用 Memos: 体验 OpenCode + Superpowers + GPT 5.2 开发需求 Memos: 杰我睿爆雷 GoReleaser 自动发布 Go 镜像到 DockerHub & GitHub Release 初识 Volta & Corepack 前端版本管理工具 部署 Beszel 把 “小鸡们” 归拢起来 Memos: Claude Code in Action 中文版教程 2025 年度回顾 Memos: 体验 tanaos-text-anonymizer-v1 NER 模型 Memos: 查询 Google 账号注册时间 Memos: 关于 Z30 在室内摄像被手机降维打击这点儿事儿 阅读《我与地坛》 Memos: Ghostty + Neovim + LazyVim Memos: 找到 Cursor 运行巨慢的一个原因 Memos: 京东家政 哈尔滨灵活就业人员医保退休待遇申领条件 记地暖不热的维修过程 Memos: 赛博菩萨 Cloudflare 又挂了 AnyTLS 软件的配置与使用 阅读《在巴东》 Memos: Web Archive 暂时离线 忆时光:十五年前我的家(动迁前夕) macOS 系统部署 Valkey 集群模式 阅读《一个名叫欧维的男人决定去死》 Memos: Cursor 服务故障部分功能不可用 阅读《丰乳肥臀》 爱人回家送奶奶 Memos: AWS 美东可用区 P0 故障(us-east-1) 2025 北京社保下限上调|个体户缴费随之上涨 铁锅重生记 不锈钢盆与放心水源改造计划 阅读《不被大风吹倒》 基于 Supabase 构建示例应用(上篇):数据库与接口 阅读《三体》之地球往事 Oracle Free 实例重装系统 非京籍个体户缴纳社保(补充):“无有效的汇总预处理信息” 解决办法 阅读《芯片简史》 阅读《简约至上:交互式设计四策略(第2版)》 阅读《审判》 阅读《统计数字会撒谎》 达达秒送骑士 日本关西系列|在动物园前站找到海南本线 观影《长安的荔枝》 Memos: 记录两个在线工具 地球 Online:外卖骑手体验报告 杜师傅夜话:附身与归途 日本关西系列|使用投放硬币的行李寄存箱 日本关西系列|将多余零钱充值到西瓜卡 日本关西系列|网上购买大阪往返白滨高速巴士 乌鲁木齐・赛里木湖 “778 老哥” 摄影摘选(转载) 使用 Restic 来备份重要数据 Backing Up Important Data with Restic Sauvegarder des données importantes avec Restic Resticで重要なデータをバックアップする Cursor 开发 Obsidian 插件记录 非京籍个体户缴纳社保(十):新增并缴纳个人所得税-工资薪金 非京籍个体户缴纳社保(九):公积金开户增员与缴费 非京籍个体户缴纳社保(八):税务申报与工商年报 非京籍个体户缴纳社保(七):社保费用申报与缴纳 非京籍个体户缴纳社保(六):医保公共服务平台 - 增员确认 非京籍个体户缴纳社保(五):北京电子税务局 - 税务报道 非京籍个体户缴纳社保(四):北京市社会保险网上服务平台 - 增员与社保卡领取 非京籍个体户缴纳社保(三):社会保险网上服务平台 - 单位信息登记 非京籍个体户缴纳社保(二):北京 e 窗通平台提交申请 非京籍个体户缴纳社保(一):概览与先期准备 养老保险零基础入门指南(速通版) 了解北京门诊看病工会“二次报销”互助金 注册 US.KG 免费域名(dpdns.org) 白嫖 Cloudflare R2 + Worker 搭建私有镜像仓库 再思 JWT 的使用场景和算法选择 黑龙江・木兰县属小村落的星空(2024) Nginx 启用 HTTPS/3 优化网站的 SSL Labs 总体评级为 A+(禁用旧协议 & 启用 HSTS) 了解 OCSP Stapling 证书吊销验证机制 山东・烟台中秋两三日(2024) 分享改造后的博客发布流程和访问链路 边缘网络:白嫖 Cloudflare R2 博客图床(DNS 国内外分流)
基于 Supabase 构建示例应用(中篇):实现 Vue 前端页面
2025-08-29 · via 东东's Blog

构建 Vue 项目

Supabase 服务的 Vue 构建官方文档:https://supabase.com/docs/guides/getting-started/quickstarts/vue

创建了一个 Github 仓库,用来存放 Vue 前端项目

克隆项目

git clone git@github.com:sincerefly/vuebase-posty.git

初始化 Vue 项目

cd vuebase-posty
npm create vue@latest .

选项

回车确认后的 Oxlint(试验阶段)和 rolldown-vite(试验阶段)都不选择,示例代码也不需要

运行三连

# 安装依赖
npm install

# 格式化代码
npm run format

# 启动
npm run dev

引入 Supabase 依赖

安装库

npm install @supabase/supabase-js

创建环境变量文件

touch .env.local

将服务地址和 Supabase Publishable Key 填入(注意替换为自己的地址和密钥)

VITE_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
VITE_SUPABASE_PUBLISHABLE_KEY=<SUBSTITUTE_SUPABASE_PUBLISHABLE_KEY>

这里有一些容易混淆的地方需要注意

02.webp

Supabase 教程页面上显示出用户的 Anon Key,看着需要使用这个 key 作为SUBSTITUTE_SUPABASE_PUBLISHABLE_KEY,那上篇中的 “sb_publishable_JToCFTxxxxxx” 又是什么,用哪个呢?

这个以 sb_publishable_ 开头的密钥,实际上就是 anon key(匿名公钥),只是 Supabase 在不同时期使用了不同的命名格式。

结论就是用谁都行,sb_publishable_ 是旧的,JWT 格式的密钥是更新的格式

新建 src/lib/supabaseClient.js 文件

import { createClient } from '@supabase/supabase-js'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

修改 src/App.vue 文件

根据我的表 Posts 做了相应调整

<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from './lib/supabaseClient'

const posts = ref([])

async function getPosts() {
  const { data } = await supabase.from('posts').select()
  posts.value = data
}

onMounted(async () => {
  await getPosts()
})
</script>

<template>
  <ul>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

显示出来三篇已发布文章(标题相同)

导出表 Types

https://supabase.com/docs/guides/api/rest/generating-types

如果网络抽风,使用本地代理 npm 设置如下 npm config set proxy socks5://127.0.0.1:7897 npm config set https-proxy socks5://127.0.0.1:7897

使用后清理 npm config delete proxy npm config delete https-proxy

# 安装命令行工具
npm i supabase@">=1.8.1" --save-dev

# 打开浏览器登录
npx supabase login

登录后,如果之前未执行过初始化,先在项目根目录运行

npx supabase init

按需选择,默认都是 N

Generate VS Code settings for Deno? [y/N] 
Generate IntelliJ Settings for Deno? [y/N] 
Finished supabase init.

获取数据库的类型定义

mkdir -p src/types

# 生成 Schema,注意替换 PROJECT_REF,就是服务器 API 地址子域名那串字符
npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > src/types/database.types.tss

Vibe Coding 环节

好了,Step by Step 到此,接下来开始氛围编程

这是一个 Vue 项目,后端是 Supabase 服务,请实现以下功能:

表结构如下:

CREATE TABLE users (
  id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  username TEXT UNIQUE CHECK (char_length(username) >= 3),
  email TEXT UNIQUE,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);


CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  title VARCHAR(255) NOT NULL,
  content TEXT,
  published_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

已经定义了策略,所有用户(匿名、登录)都可以获取到已发布的文章,即 published_at 字段不为空的记录;用户可以创建、修改自己的文章。

以上是服务端一些表结构,另外已经通过 supabase cli 导出了数据库字段类型的 Schema,在 src/types/database.types.tss

前端功能说明

1. 页面顶部有两栏,一个是“广场”,另一个是“我的”栏目,右上角有注册、登录功能,广场展示所有已发布的文章,我的栏目,如果未登录,通过页面上的文字提示请先登录,登录后展示所有用户的文章,登录后的右上角展示用户名,点击弹出下拉,有设置和登出、设置页面目前可以设置语言偏好;
2. 用户我的页面的文章,需要显示是否发布,可以根据单选项过滤全部、已发布、未发布三个状态,文章后方应该有编辑、发布的按钮,支持修改标题和内容;
3. 需要支持多语言,目前仅需要适配中文、英文两种语言,用户选择语言后,应该在浏览器本地进行缓存;

编码风格说明

1.首先,保持现代化、但不要使用过于花哨的颜色,简洁、小清新为主
2.代码实现应注意解耦合和封装,不要多个逻辑放到一个大文件中
3.API 接口和数据库操作需要符合 Supabase 的使用规范和习惯

正好打算试试 Trae,不过目前的智能程度,真是爱了... ⬇️

还是配置 Proxy,使用 Cursor

3 Hours Later...

06.webp

页面功能初步完成

07.webp

多语言也支持的良好

调整策略

因为上篇实验中,缺少部分策略,可以在 Supabase 面板删除掉所有策略,重新创建本示例所需的策略

TODO 再核对下:

-- posts 表

alter policy "允许匿名和登录用户查看所有已发布文章"
on "public"."posts"
to anon, authenticated
using (
  (published_at IS NOT NULL)
);

create policy "允许登录用户创建自己的文章"
on "public"."posts"
for insert
to authenticated
with check (
  -- 确保用户只能插入自己的帖子
  user_id = auth.uid() 
);

create policy "允许登录用户删除自己的文章"
on "public"."posts"
as PERMISSIVE
for DELETE
to authenticated
using (
  auth.uid() = user_id
);

create policy "允许登录用户查看自己所有文章" -- 包含未发布
on "public"."posts"
as PERMISSIVE
for SELECT
to authenticated
using (
  auth.uid() = user_id
);

alter policy "允许登录用户更新自己的帖子"
on "public"."posts"
to authenticated
using (
  (auth.uid() = user_id)
with check (
  (auth.uid() = user_id)
);

alter policy "用户每天只能插入10篇文章"
on "public"."posts"
to authenticated
with check (
  ((auth.uid() = user_id) AND (( SELECT count(*) AS count FROM posts posts_1 WHERE ((posts_1.user_id = auth.uid()) AND (posts_1.created_at > (now() - '1 day'::interval)))) < 10))
);

-- users 表

create policy "允许用户查看自己的用户信息"
on "public"."users"
for select
to authenticated
using (
(select auth.uid()) = id
);

CREATE POLICY "允许用户更新自己的用户信息" 
ON "public"."users"
FOR UPDATE 
USING (auth.uid() = id);

通过 Cloudflare Page 部署

因为编译后是纯前端页面,所以可以托管到 Pages 服务,可选择性很多,优先国外,因为国内 Page 服务可持续性 be like

08.webp

Github Pages、Cloudflare Pages、Vercel 作为 Demo 放到哪里都足够,根据我的个人习惯,选择部署到 Cloudflare Pages,因为我有一个域名由 Cloudflare 管理,绑定自定义域名时可以纵享丝滑


先上传前端代码到 Github,我的仓库是:sincerefly/vuebase-posty

登录 Cloudflare 面板

09.webp

选择 Workers & Pages,点击创建

注意先切换到 Pages,然后再点击 Get started

11.webp

选择项目后下一步

选择 Vue Framawork,参数默认,应该还记得 .env.local 文件,将里面的 VITE_SUPABASE_URLVITE_SUPABASE_PUBLISHABLE_KEY 设置到此处环境变量

点击部署,稍后可以看到服务已部署

13.webp

服务地址:https://vuebase-posty.pages.dev

.pages.dev 是 Cloudflare 提供的域名,子域名是服务名,重复会追加随机字符。

14.webp

添加自定义域名(可选)

15.webp

由 CF 托管的域名,无需手动配置

16.webp

稍等片刻

17.webp

地址:https://posty.donx-done.xyz

配置 Supabase 服务 URL 地址

18.webp

配置完成后,到页面进行注册测试,当头两棒子

{"code":"over_email_send_rate_limit","message":"For security purposes, you can only request this after 49 seconds."}

{"code":"over_email_send_rate_limit","message":"email rate limit exceeded"}

这是一个 Supabase 配置,位置在 Authentication 下的 Rate Limit

19.webp

改成 20 封邮件后,可以找临时邮箱进行注册验证

Supabase 注册 URL 自动登录逻辑

  1. 用户点击邮件中的确认链接(http://web-host/#access_token=xxx&refresh_token=xxx&type=recovery)
  2. Supabase 客户端自动检测 URL 参数(detectSessionInUrl: true),自动创建 session 并触发 onAuthStateChange 事件
  3. 认证状态监听器处理 (src/stores/auth.ts)
  4. 应用初始化 (src/App.vue)

我也没仔细看,因为全程氛围编程,没写几行前端代码

记在最后

Vibe Coding 一些心得,描述需求时要全面,但让其实现代码时要分步实现。Debug 时,让其添加 Console 日志,将问题日志提交给它,定位会更快、更准确

前端使用 Vue 开发,部署到了 Cloudflare:https://posty.donx-done.xyz

20.webp

前端代码仓库:sincerefly/vuebase-posty

本文阶段性的目标已达成,这篇想了想,定为「中篇」,Supabase 还有不少值得探索的功能,放到「下篇」学习记录。