袁宝工具箱:小程序卡片化首页的设计与实现

最近开发的小程序「袁宝工具箱」:首页没有固定布局,而是由 10 张功能卡片搭成的仪表盘——每张卡片都能显隐、排序,配置还带本地缓存与云端同步。这篇介绍卡片体系的功能点与实现思路。

袁宝工具箱小程序码
扫码体验「袁宝工具箱」

卡片化首页

工具箱类小程序的通病是功能越加越多、首页九宫格越来越挤。我的做法是反过来:把首页做成一张可由用户自由编排的画布,每个功能是一张卡片,”用不用、排哪里”都交给用户。

技术底座是 uni-app(Vue3 + TypeScript)+ cool-uni 脚手架,一套代码同时跑微信小程序端。

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pages/index/
├── index.vue # 首页:按 order 渲染卡片
├── my.vue # 卡片管理页:长按拖拽排序 + 显隐开关
├── components/
│ ├── life-bar.vue # 人生进度卡片
│ ├── weather-card.vue # 天气卡片
│ ├── calendar-view.vue # 日历卡片
│ ├── lunar-info.vue # 农历信息卡片
│ ├── memo-card.vue # 备忘录卡片
│ ├── relationship-card.vue # 亲戚关系计算器
│ ├── math-card.vue # 算术练习
│ ├── scan-card.vue # 扫码助手
│ ├── china-tour-card.vue # 环中国游助手
│ ├── bird-card.vue # 鸟类科普
│ └── tabbar.vue # 自定义底部导航
└── composables/
├── useCardConfig.ts # 卡片配置中枢(本地缓存 + 云端同步)
├── useCardVisibility.ts # 显隐 / 排序适配层
├── useMemoCount.ts # 备忘录数量跨页单例
├── useWeather.ts # 天气数据
├── useLunar.ts # 农历换算
└── chinaTourRoutes.ts # 环中国游路线数据

十张功能卡片

卡片 功能点
🌍 人生进度 “地球 Online 时间线”:填入生日,实时算出已活天数,在预期寿命进度条上定格。支持按年/按预期时间两种模式
⛅ 天气 当前天气 + 未来预报,城市可切换且选择持久化
📅 日历 翻月浏览、年月快速跳转(2000-2030),选中日期与其他卡片联动
🏮 农历信息 干支纪年 + 农历月日展示
📝 备忘录 数量角标 + 最近一次提醒摘要,支持农历提醒(按年滚动到下一年)
👨‍👩‍👧 亲戚关系 亲戚关系计算器入口
➗ 算术练习 口算练习入口
📷 扫码助手 扫码工具入口
🚗 环中国游助手 沿边境环游中国的路线助手
🐦 鸟类科普 图鉴浏览,卡片角标展示分类总数(公开接口,无需登录)

前三张(人生进度/天气/日历)是”信息型”卡片,直接在首页展开内容;后七张是”入口型”卡片,以图标 + 角标形式快速跳转。

功能点一:数据驱动的卡片渲染

首页没有 if-else 长列表,而是一份卡片清单驱动一切:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 卡片清单(新增卡片在此登记)
export const CARD_KEYS = [
{ key: "lifeBar", label: "人生进度" },
{ key: "weatherCard", label: "天气卡片" },
{ key: "calendarView", label: "日历卡片" },
{ key: "lunarInfo", label: "农历信息" },
{ key: "relationshipCard", label: "亲戚关系" },
{ key: "mathCard", label: "算术练习" },
{ key: "scanCard", label: "扫码助手" },
{ key: "chinaTour", label: "环中国游助手" },
{ key: "memo", label: "备忘录" },
{ key: "bird", label: "鸟类科普" },
] as const;

每张卡片对应一份配置 { visible, order, config }:显示状态、排序权重、卡片私有配置(天气城市、生日、预期时间……)。首页模板只是一段 v-for="key in cardOrder",按 order 排序后逐个渲染:

1
2
3
4
5
<template v-for="key in cardOrder" :key="key">
<life-bar v-if="key === 'lifeBar' && cardVisibility.lifeBar" />
<weather-card v-else-if="..." />
...
</template>

新增一张卡片 = 登记一行清单 + 写一个组件,渲染、排序、显隐、同步全部复用现有体系。

功能点二:长按拖拽排序

排序在”我的”页完成,拖拽是手写的(小程序没有好用的现成方案):

  • longpress 触发,记录起始触点与当前卡片下标,进入拖拽态
  • 移动时计算触点偏移,实时映射到被拖卡片的 translate(dx, dy),其余卡片让位
  • 松手后按最终位置重排 order,落位瞬间关闭 transition 再恢复,避免被拖项从旧偏移”滑”回列表的穿帮
  • 拖拽激活时锁住页面滚动(page-meta 的 overflow: hidden)

排序结果写回 order 权重,首页立即响应。

功能点三:配置的本地缓存 + 云端同步

这是整个卡片体系里最值得展开的部分。useCardConfig 是一个模块级单例 Composable,同时是”单一数据源”:

数据流

1
2
3
4
5
内存 reactive cards ──persist──▶ 本地缓存
▲ │
applyRemote syncToRemote(300ms 防抖)
│ ▼
└────── 远端 all 接口 ◀── 远端 sync 接口
  • 首屏可用:先同步读本地缓存渲染,再异步拉远端
  • 登录前后衔接:未登录只读本地;登录完成后 refreshFromRemote 重新决策
  • 防抖上行:本地变更 300ms 防抖后批量同步,避免高频写云端

冲突决策

本地和云端都有配置时怎么办?策略分四种:

本地 云端 行为
无 有 云端覆盖本地
有 无 本地推上云端(新账号首次登录)
都有,内容一致 静默采用云端
都有,内容不一致 弹窗让用户选:用云端 / 用本地(选本地则强制上行覆盖)

两份配置比较前先做序列化排序(按 cardKey 排序后 JSON 比较),保证顺序无关的一致性判断。

两个容易踩的坑

  1. remoteSynced 的语义:拉到云端数据后置位,此后普通同步不再自动上行,防止旧本地数据覆盖云端。但用户主动改动(选城市、改生日)必须强制上行,所以 setCardConfig / setCardVisible / setCardOrder 都有 forceSync 参数——拉数据用的 all 接口读的正是这份最新缓存,下次进入才能拿到所选城市。
  2. 旧缓存迁移:早期版本把排序和显隐散落在 card_order、card_visible_* 等独立 storage key 里,新版读取时逐一识别并合并进新结构,老用户升级无感。

功能点四:备忘录卡片的”最近提醒”

备忘录卡片不只是数量角标,还要展示最近一次即将到来的提醒,这里有两个细节:

  • 阴历提醒按年滚动:备忘录存的是阳历日期,阴历类型的提醒需要在客户端换算”今年的农历X月X日对应阳历几号”——后端旧逻辑用全局查找会把已过期的阴历提醒选上来,改为客户端从”开启提醒”的列表里挑”今天之后最近的一次”
  • 跨页面共享单例:useMemoCount 是模块级单例,列表/详情页增删改后直接刷新同一份数据;首页则在 onShow 统一触发刷新,避免重复请求。文案也有讲究:提醒日期跨年时带上年份,避免用户误以为是今年已过期的日期

小结

卡片化首页的本质是**把”功能列表”抽象成”配置数据”**:

  • 渲染由 CARD_KEYS + order + visible 驱动,加功能不动首页
  • 用户偏好(排序、显隐、卡片私有配置)统一收口到一个 Composable,本地与云端双写
  • 信息型卡片(天气/日历/农历/人生进度)直接在首页出内容,入口型卡片做跳转,轻重搭配

后续加新功能,只需要”登记一行 + 写一个卡片组件 + (可选)在管理页加个图标”,整个体系自动接管剩下的工作。


袁宝工具箱:小程序卡片化首页的设计与实现
https://cszy.top/20260915-袁宝工具箱小程序卡片化首页/
作者
csorz
发布于
2026年9月15日
许可协议