返回文章列表

Plocr-chef 技术实现:Supabase + FLIP 动画 + Lottie + 液态玻璃 UI

上一篇写了流程,这篇写代码

Plocr-chef 是一个有 11 个页面的微信小程序,后端用 Supabase 免费计划,前端全是原生 WXML/WXSS/JS,没有用任何 UI 框架。这篇记录它的技术实现要点。

整个项目前端代码约 4000 行,后端零代码(只有 Supabase 的 REST API)。

一、Supabase:CRUD + 图片 + 保活

后端用的是 Supabase 免费计划,提供一个 PostgreSQL 数据库和对象存储。没有写后端代码,直接从微信小程序发 REST 请求。

封装了一个 utils/supabase.js,暴露 6 个函数:

函数HTTP 方法说明
select(table, params)GET带查询参数的查表
insert(table, data)POST新增记录
update(table, id, data)PATCH按 id 更新
remove(table, id)DELETE按 id 删除
uploadImage(filePath)POST上传到 Supabase Storage
deleteImage(imageUrl)DELETE从 Storage 删除

所有请求通过 wx.request 发送,请求头带 apikey 和 Bearer token。上传图片走 multipart,上传后返回公开 URL 存数据库。

数据库三张表:

dishes    — id, name, category, price, recipe, image_url
orders    — id, date, status, items (jsonb), total
wishlist  — id, dish_name, who, note, status

还有一个值得一提的细节:保活机制。Supabase 免费计划超过 7 天不活动会暂停数据库。所以在 app.js 里加了一个 keepWarm(),每 24 小时 ping 一次 /rest/v1/dishes


二、FLIP 动画:卡片展开详情

首页推荐菜品的卡片,点击后不是跳转页面,而是从卡片当前位置展开成一个详情的浮层。这是用 FLIP(First, Last, Invert, Play)动画实现的。

核心逻辑在 utils/flip.js 里:

  1. 点击时用 createSelectorQuery 获取卡片当前位置和尺寸(First)
  2. 浮层渲染后拿到它的最终位置(Last)
  3. 计算位移差和尺寸差(Invert)
  4. this.animate() 执行 9 个关键帧的展开动画(1.25s ease-in-out)
  5. 关闭时执行逆动画(8 个关键帧,0.9s)缩回原位

效果就是卡片像从页面上「长」出来一样顺滑。这是整个项目里用户感知最明显的交互亮点。

📐 FLIP 技术要点

createSelectorQuery 获取坐标 · 9 帧展开(1.25s)· 8 帧收回(0.9s)· spring cubic-bezier 缓动


三、Lottie 动画:状态反馈

为了替代微信小程序里比较单调的 loading/empty/success 反馈,我封装了一个 Lottie 动画组件

<lottie-player type="loading" width="200" height="200"></lottie-player>
<lottie-player type="empty" width="300" height="300"></lottie-player>
<lottie-player type="success" width="150" height="150" loop="{{false}}"></lottie-player>

三组动画数据内嵌在组件 JS 里(不是从网络加载),用一个 npm 包 lottie-miniprogram 渲染到 <canvas type="2d"> 上:

  • loading:3 个弹跳的圆点
  • empty:一个冒着热气的锅
  • success:圆圈 + 勾

这个组件被用在了首页、购物车、订单、菜品管理、统计、心愿单等 6 个页面里,统一了所有状态反馈的视觉风格。


四、液态玻璃 UI 设计系统

整个项目的视觉风格是「液态玻璃」——温暖、通透、柔软。用 26+ 个 CSS 变量定义了一套完整的设计 Token:

--bg-primary: #fdf6f0
--bg-card: rgba(255, 255, 255, 0.68)
--glass-blur: blur(20px)
--glass-saturate: saturate(180%)
--radius-card: 24rpx
--radius-sm: 12rpx
--shadow-card: 0 4rpx 20rpx rgba(0,0,0,0.06)
--accent: #a0522d
--accent-light: rgba(160, 82, 45, 0.1)
--text-primary: #3d2b1f
--text-secondary: rgba(61, 43, 31, 0.6)

支持深色模式,每个组件都有显式的暗色覆盖(TabBar、返回按钮、卡片、弹窗、购物车栏、分类标签、表单输入)。深色模式通过 @media (prefers-color-scheme: dark) 媒体查询自动切换。


五、11 个页面的路由与交互

底部 TabBar 只有两个入口:

TabBar
├── 首页 ──→ 全部菜单 ──→ 购物车 ──→ 提交订单
└── 管理 ──→ 菜品管理 ──→ 新增/编辑菜品
           ├── 订单管理 ──→ 食谱查看
           ├── 数据统计
           └── 心愿单

所有页面跳转使用 navigateTo + animationType: 'slide-in-right'(300ms),返回用 navigateBack + slide-out-right

每个页面都有完善的边界处理:

  • 加载态(骨架屏或 Lottie loading)
  • 空态(Lottie empty + 提示文案)
  • 网络失败(本地备份数据兜底)
  • 防重复提交(submitting 标志位)
  • 输入校验(价格非数字/负数、菜品名必填)

六、本地备份

Supabase 免费计划可能暂停,数据可能丢。所以每次成功拉取数据后,都会调用 app.backupData(type, data) 把数据存到 wx.setStorage。网络失败时自动读取本地备份。

同样,购物车数据也在每次修改后同步到 wx.setStorage,App 启动时自动恢复。

这个项目让我最满意的地方是:它证明了在 2026 年,一个人+AI 可以用纯免费的服务搭建一个完整可用的微信小程序。不需要后端、不需要服务器、不需要上架应用商店。

下一篇开始写另一个项目「碎片」——手绘草图到代码的设计之旅。