This commit is contained in:
2026-06-25 18:24:29 +08:00
commit a1190d7d9a
90 changed files with 5459 additions and 0 deletions

140
docs/01-架构设计.md Normal file
View File

@@ -0,0 +1,140 @@
# 01 · 架构设计
## 1. 技术选型理由
**原生小程序 + 云开发**,因为:
- 云开发是微信官方为原生小程序量身打造,集成度最高、免运维、免自建鉴权。
- 汪圈 V1 是单机闭环无跨端App / H5 / 支付宝)需求,原生比 Taro / uni-app 更直接。
- 云函数里 `cloud.getWXContext().OPENID` 直接拿到可信用户标识,省掉整套登录鉴权后端。
## 2. 整体架构
```
┌─────────────────────────────────────────────┐
│ 微信小程序客户端 │
│ pages/ (12屏) · components/ · utils/ │
│ globalData + 事件总线 │
└───────────────────┬─────────────────────────┘
│ wx.cloud.callFunction
┌────────────┴────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ 云函数 │ │ 云数据库 │
│ login │◄───────►│ users │
│ dogManage │ │ dogs │
│ walkSave │ │ walks │
│ walkManual │ │ dog_achievements│
│ getDogDetail │ └───────────────┘
│ getHistory │
└───────────────┘ │
│ ▼
▼ ┌───────────────┐
成就判定/统计累加 │ 云存储 │
│ 头像/照片/分享图 │
└───────────────┘
```
**核心原则:写操作 + 敏感计算全部走云函数**,客户端只读(或经云函数读聚合结果)。连续天数、成就解锁、补记规则等绝不在客户端算。
## 3. 工程目录结构
```
Pet3/
├── cloudfunctions/ # 云函数Node.js
│ ├── login/ # 登录 + 首次建用户
│ │ ├── index.js
│ │ └── package.json
│ ├── dogManage/ # 狗狗增改查
│ ├── walkSave/ # 保存遛狗 + 触发统计/成就
│ ├── walkManual/ # 手动补记
│ ├── getDogDetail/ # 狗狗主页聚合数据
│ └── getHistory/ # 历史/记录 Tab 列表聚合
├── miniprogram/
│ ├── app.js # 初始化 wx.cloud、globalData
│ ├── app.json # 路由、tabBar、分包
│ ├── app.wxss # 全局样式 + 设计 token
│ ├── pages/
│ │ ├── login/ # 0a 登录
│ │ ├── dog-edit/ # 0b 建档/编辑(复用)
│ │ ├── home/ # 1 我的tab
│ │ ├── walking/ # 2/2b 遛狗中(含暂停态)
│ │ ├── summary/ # 3 成果卡
│ │ ├── dog-detail/ # 4 狗狗主页
│ │ ├── history/ # 5 遛狗历史
│ │ ├── records/ # 6/6b 记录tab含空状态
│ │ └── achievements/ # 7 完整成就页
│ ├── components/
│ │ ├── dog-card/ # 狗狗卡片
│ │ ├── walk-row/ # 历史/记录行
│ │ ├── stat-grid/ # 统计三宫格
│ │ ├── achievement-medal/ # 成就勋章(解锁/锁定)
│ │ ├── multi-dog-sheet/ # 0c 多狗选择弹层
│ │ └── manual-add-sheet/ # 补记弹层
│ ├── utils/
│ │ ├── cloud.js # callFunction 统一封装 + 错误处理
│ │ ├── format.js # 时长/距离/日期格式化
│ │ ├── dogStep.js # 狗步换算(体型档位/步幅/体重校准)
│ │ └── store.js # 全局状态 + 订阅
│ └── images/ # 本地图标/占位图
├── docs/ # 设计文档(本目录)
├── wangquan-prototype.html # 交互原型
└── project.config.json
```
## 4. 全局状态store
不引入重型状态库。`miniprogram/utils/store.js` 维护:
```js
const store = {
userInfo: null, // { openid, nickname, avatar, joinedAt, walkCount }
dogs: [], // 当前用户的狗狗列表
currentDogId: null, // 默认/上次选中的狗
walkSession: null, // 进行中的遛狗会话(见下)
};
```
**遛狗会话walkSession** 是 V1 最关键的运行时状态,需支持小程序退到后台后恢复:
```js
walkSession = {
dogIds: ['xxx'], // 本次遛的狗(支持多狗)
startAt: 1719230000000,
pausedTotal: 0, // 累计暂停时长(ms),结算时扣除
pausedAt: null, // 当前暂停起点null=进行中
pooped: false, // 便便打卡
locationEnabled: false, // 是否获得定位授权
distanceMeters: null, // GPS 累计距离;未授权为 null
lastLocation: null, // 最近一次有效定位点,用于增量累计距离
status: 'walking' | 'paused',
}
```
落地策略:会话写入 `wx.setStorageSync('walkSession')`App `onShow` 时恢复,避免锁屏/切后台丢失计时。计时基于 `startAt` 与当前时间差实时计算,**不依赖 setInterval 累加**(后台被冻结也不丢)。定位为可选增强:获得授权时累计 GPS 距离并换算狗步;未授权时 `distanceMeters` / `dogStepsByDog``null`,界面隐藏距离与狗步,闭环仍成立。
## 5. 设计 Token迁移自原型
原型 CSS 变量直接转为 `app.wxss` 全局变量,保证视觉一致:
| Token | 值 | 用途 |
|-------|-----|------|
| `--bg` | `#F2EDE6` | 页面背景 |
| `--surface` | `#FBF8F3` | 卡片 |
| `--ink` | `#2E2A24` | 主文字 |
| `--ink-2` | `#6E665B` | 次文字 |
| `--accent` | `#E8A04B` | 主色(按钮/FAB |
| `--accent-deep` | `#C77E2E` | 主色深 |
| `--success` | `#6B8E5A` | 遛狗中 |
| `--warn` | `#D98A4E` | 暂停态 |
| `--danger` | `#C25A4E` | 结束/二次确认 |
> 小程序 WXSS 支持 CSS 变量,可直接 `var(--accent)`。不引第三方组件库,全部基础 UI按钮、卡片、chip、toggle、勋章等按原型纯手写为 `components/` 下的可复用组件。

136
docs/02-数据模型.md Normal file
View File

@@ -0,0 +1,136 @@
# 02 · 数据模型(云数据库)
云数据库为文档型(类 MongoDB。共 4 个集合。约定:`_openid` 由云开发自动写入作为归属与权限依据时间统一存毫秒时间戳Number
---
## 1. `users` — 用户
| 字段 | 类型 | 说明 |
|------|------|------|
| `_id` | String | 自动 |
| `_openid` | String | 微信 openid唯一 |
| `nickname` | String | 昵称,默认「铲屎官」 |
| `avatar` | String | 云存储 fileID可空 |
| `walkCount` | Number | 累计遛狗次数(冗余,展示用) |
| `joinedAt` | Number | 加入时间戳 |
| `createdAt` | Number | 创建时间 |
> 「加入 92 天」由 `joinedAt` 客户端算;「遛狗 87 次」读 `walkCount`。
---
## 2. `dogs` — 狗狗
| 字段 | 类型 | 说明 |
|------|------|------|
| `_id` | String | 狗狗 ID |
| `_openid` | String | 归属用户 |
| `name` | String | **必填**,名字 |
| `breed` | String | **必填**,犬种(如「柯基」) |
| `sizeLevel` | String | 体型档位:`xs`/`s`/`m`/`l`/`xl`,由犬种映射;未知犬种由用户手动选择 |
| `baseStride` | Number | 档位基础步幅,单位米/步 |
| `stride` | Number | 最终步幅,填体重时在 `baseStride` 上校准,封顶 ±15% |
| `gender` | String | `male` / `female` / null |
| `ageStage` | String | `puppy` / `adult` / `senior` / null |
| `ageText` | String | 展示文案,如「成犬 3 岁」,可空 |
| `weight` | Number | kg可空二次校准狗步 |
| `avatar` | String | 云存储 fileID可空 |
| `stats` | Object | 累计统计(见下,冗余加速主页) |
| `createdAt` | Number | 建档时间 |
**`stats` 内嵌对象**(每次遛狗结算时在云函数原子更新):
```js
stats: {
totalWalks: 87, // 总次数
totalDistance: 142.0, // 总公里
totalSteps: 0, // 总狗步(可选)
currentStreak: 7, // 连续天数
lastWalkDate: '2026-06-24', // 最近一次有效遛狗的日期(yyyy-mm-dd),算连续用
}
```
> 仅「名字 + 犬种」必填——降低首次门槛。`sizeLevel/baseStride/stride` 由 `utils/dogStep.js` 按「犬种 → 体型档位 → 步幅」计算;中华田园犬、串串、找不到的犬种必须让用户手动选体型档位。
---
## 3. `walks` — 遛狗记录
| 字段 | 类型 | 说明 |
|------|------|------|
| `_id` | String | 记录 ID |
| `_openid` | String | 归属用户 |
| `dogIds` | Array<String> | 本次遛的狗(支持多狗一起遛) |
| `startAt` | Number | 开始时间戳 |
| `endAt` | Number | 结束时间戳 |
| `duration` | Number | 净遛狗秒数(已扣暂停) |
| `distance` | Number | 公里,未授权定位时为 null |
| `dogStepsByDog` | Object | `{ [dogId]: steps }`,按每只狗的 `stride` 分别换算;未授权定位时为 null |
| `pooped` | Boolean | 便便打卡 |
| `weather` | String | `sunny`/`cloudy`/`rainy`,可空 |
| `mood` | String | 状态标签,可空 |
| `note` | String | 备注,可空 |
| `photos` | Array<String> | 云存储 fileID可空 |
| `source` | String | `realtime` / `manual`,对应 PRD 的数据来源 |
| `isManual` | Boolean | 是否手动补记;兼容展示判断,等价于 `source === 'manual'` |
| `countsForRanking` | Boolean | 是否计入排行榜;补记恒为 `false` |
| `walkDate` | String | `yyyy-mm-dd`,按本地时区,连续/分组用 |
| `createdAt` | Number | 入库时间 |
> 成果卡所有补充字段photos/weather/mood/note全部选填跳过也能保存。`dogStepsByDog` 仅用于拟人化展示;成就与未来排行榜只使用距离、时长、连续天数等客观量。
---
## 4. `dog_achievements` — 成就解锁记录
成就**按狗维度**(原型「成就墙 · 豆豆」)。成就定义不入库,固化在代码 `utils/achievements.js`(含 key、名称、描述、判定规则、图标。本集合只存「某狗解锁了某成就」。
| 字段 | 类型 | 说明 |
|------|------|------|
| `_id` | String | 自动 |
| `_openid` | String | 归属用户 |
| `dogId` | String | 狗狗 ID |
| `achievementKey` | String | 成就标识,如 `week_streak` |
| `unlockedAt` | Number | 解锁时间戳 |
唯一性:`(dogId, achievementKey)` 不重复解锁(云函数判定前先查重)。
**成就 key 参考**(来自原型):
| key | 名称 | 解锁条件 |
|-----|------|---------|
| `first_walk` | 首遛 | 第一次记录遛狗 |
| `week_streak` | 一周不断 | 连续遛狗满 7 天 |
| `month_streak` | 满月坚持 | 连续遛狗满 30 天 |
| `rainy_warrior` | 雨天战士 | 雨天也坚持遛狗 |
| `hundred_km` | 百里同行 | 累计满 100 公里 |
| `early_bird` | 早起鸟 | 7 点前遛狗 10 次 |
---
## 5. 索引
| 集合 | 索引字段 | 说明 |
|------|---------|------|
| `users` | `_openid`(唯一) | 登录查重 |
| `dogs` | `_openid` | 列出我的狗 |
| `walks` | `_openid` + `walkDate`(降序) | 历史列表、连续判定 |
| `walks` | `dogIds`(多键) | 按狗查记录 |
| `dog_achievements` | `dogId` + `achievementKey`(联合唯一) | 查重与成就墙 |
## 6. 数据库权限(安全规则)
全部集合设为 **「仅创建者可读写」**,写操作再经云函数二次校验。客户端只允许读自己的数据;统计累加、成就解锁等只能由云函数(管理员权限)写,防止刷量。
```json
// 示例walks 集合权限
{
"read": "doc._openid == auth.openid",
"write": false // 客户端禁写,统一走云函数
}
```

195
docs/03-云函数设计.md Normal file
View File

@@ -0,0 +1,195 @@
# 03 · 云函数设计
约定:所有云函数用 `wx-server-sdk`,统一返回 `{ ok: true, data }``{ ok: false, code, msg }``openid` 一律从 `cloud.getWXContext().OPENID` 取,**绝不信任客户端传入的 openid**。
---
## 1. `login` — 登录 / 首次建用户
**职责**:拿 openid若用户不存在则建档返回用户信息 + 是否已有狗狗(决定跳建档还是首页)。
```
入参: {}
出参: {
ok, data: {
userInfo: { openid, nickname, avatar, walkCount, joinedAt },
hasDog: Boolean // false → 前端跳 0b 建档true → 跳首页
}
}
```
逻辑:`users``_openid`,无则插入默认用户;再 `dogs` count 判断 `hasDog`
---
## 2. `dogManage` — 狗狗增 / 改 / 查
**职责**:建档、编辑、列表(建档与编辑复用,由 `action` 区分)。
```
入参: {
action: 'create' | 'update' | 'list',
dog?: { _id?, name, breed, sizeLevel?, gender, ageStage, ageText, weight, avatar }
}
出参: { ok, data: dog | dogs[] }
```
要点:
- 服务端校验 `name``breed` 非空。
-`breed` 查表得到体型档位 `sizeLevel`;中华田园犬、串串、找不到的犬种必须由前端让用户手动选择 `sizeLevel` 后再提交。
-`sizeLevel` 得到 `baseStride`,填了 `weight` 时计算最终 `stride``baseStride * (1 + (weight - 档位标准体重) * 0.005)`,修正幅度封顶 ±15%。
- `create` 时初始化 `stats` 全 0。
- `update` 校验该 dog 的 `_openid` == 调用者,防越权改他人狗。
---
## 3. `walkSave` — 保存遛狗(核心)
**职责**:写入一条 `walks`**事务式**更新每只狗 `stats`,触发成就判定;并支持成果卡页回写选填字段。这是 V1 最重的云函数。
### 3.1 结束遛狗:`action='finish'`
```
入参: {
action: 'finish',
dogIds: [String],
startAt, endAt, duration,
distance?,
pooped
}
出参: {
ok, data: {
walkId,
dogStepsByDog,
newAchievements: [{ key, name, desc }] // 本次新解锁,成果卡展示
}
}
```
执行步骤:
1. 校验 `dogIds` 均归属当前 openid未建档任何狗时拒绝保存。
2. 计算 `walkDate`(按用户本地时区,前端传时区偏移或统一 +8
3. 插入 `walks``source='realtime'``isManual=false``countsForRanking=true`
4. 获得定位授权时写入 `distance`,并按每只狗的 `stride` 在服务端计算 `dogStepsByDog`;未授权时 `distance``dogStepsByDog``null`,只保存时长。
5. 对每个 dogId
- `totalWalks += 1``totalDistance += distance||0``totalSteps += dogStepsByDog[dogId]||0`
- **连续天数**:比较 `lastWalkDate``walkDate`——同日不变;昨日则 `currentStreak += 1`;更早则重置为 1。更新 `lastWalkDate`
-`db.command.inc()` 做原子自增,避免并发覆盖。
6. `users.walkCount += 1`
7. 调成就判定(见 `achievementCheck`),收集新解锁返回。
### 3.2 成果卡补充字段:`action='attach'`
```
入参: {
action: 'attach',
walkId,
weather?, mood?, note?, photos?
}
出参: { ok, data: { walkId, newAchievements?: [{ key, name, desc }] } }
```
要点:
- `weather/mood/note/photos` 全部选填,用户跳过也不影响 `finish` 已保存的核心记录。
- 只允许回写当前 openid 自己的 walk。
-`weather='rainy'`,可在 attach 后补触发「雨天战士」等场景成就,并返回新增成就给成果卡刷新。
- 旧记录回看不复用 attach后续单次记录详情页若支持编辑应单独定义记录编辑能力。
---
## 4. `walkManual` — 手动补记
**职责**:补记一次遛狗。规则与 `walkSave` 不同。
```
入参: { dogId, startAt, duration, distance? }
出参: { ok, data: { walkId, newAchievements?: [{ key, name, desc }] } }
```
关键差异(**业务规则,必落地**
- `source='manual'``isManual=true``countsForRanking=false`(不计排行榜,防刷量)。
- **可维持连续打卡**:同样参与 `currentStreak` 计算(不断签)。
- 不触发「分享/成果卡」类成就,但可触发「连续」类成就。
- 列表展示带「补记」标识(前端按 `isManual` 渲染)。
---
## 5. `getDogDetail` — 狗狗主页聚合
**职责**:一次返回狗狗主页所需全部数据,减少往返。
```
入参: { dogId }
出参: {
ok, data: {
dog: {...}, // 含 stats
achievements: { // 成就墙
unlocked: [{key,name,unlockedAt}],
total: 12
},
recentWalks: [walk, ...] // 近期记录(取最近 5~10 条)
}
}
```
---
## 6. `getHistory` — 历史 / 记录 Tab 聚合
**职责**:返回当前用户全部狗狗的遛狗记录列表,供历史页和记录 Tab 复用,减少前端跨集合拼装。
```
入参: { dogId?, page?, pageSize? }
出参: {
ok, data: {
list: [{ walk, dogNames, manualLabel }],
hasMore: Boolean
}
}
```
要点:
- 默认查当前 openid 下全部记录;传 `dogId` 时只查某只狗。
-`walkDate/startAt` 倒序返回,可由前端按周或日期分组。
- 返回项带狗名、是否补记标识,避免列表层再多次请求 `dogs`
- 只读,不写库。
---
## 7. 内部模块:`achievementCheck`(被 walkSave/walkManual 复用)
不是独立云函数,是云函数内共享的判定模块(也可做成被 `cloud.callFunction` 内部调用的函数)。
```
输入: dogId, 该狗最新 stats, 本次 walk含 weather/时间)
流程:
for 每个成就定义:
if 已解锁(查 dog_achievements) → skip
if 满足条件(基于 stats / walk):
插入 dog_achievements
收集进 newAchievements
返回: newAchievements[]
```
判定基于 **连续 / 累计 / 场景**,不基于「最多 / 最快」。
---
## 8. 云函数清单速查
| 云函数 | 触发时机 | 写库 |
|--------|---------|------|
| `login` | App 启动 | users |
| `dogManage` | 建档/编辑/进首页 | dogs |
| `walkSave` | 结束遛狗、成果卡补充字段回写 | walks, dogs, users, dog_achievements |
| `walkManual` | 历史页补记保存 | walks, dogs, dog_achievements |
| `getDogDetail` | 进狗狗主页 | 只读 |
| `getHistory` | 进历史页 / 记录 Tab | 只读(跨全部狗狗,附狗名) |
> 后续若上排行榜pending新增定时触发云函数做聚合按 `countsForRanking=true` 统计。

View File

@@ -0,0 +1,89 @@
# 04 · 页面与路由
## 1. 页面清单(对应原型 12 屏)
| 原型 Frame | 页面/组件 | 路径 | 类型 | 说明 |
|-----------|----------|------|------|------|
| 0a 登录 | login | `pages/login/login` | 普通页 | 微信授权登录 |
| 0b 建档/编辑 | dog-edit | `pages/dog-edit/dog-edit` | 普通页 | 建档与编辑复用,`?id=` 区分 |
| 0c 多狗选择 | multi-dog-sheet | `components/multi-dog-sheet` | 组件(弹层) | 点遛狗时多狗才弹 |
| 1 我的 | home | `pages/home/home` | **Tab** | 启动默认页 |
| 2/2b 遛狗中 | walking | `pages/walking/walking` | 普通页 | 进行/暂停同页切状态 |
| 3 成果卡 | summary | `pages/summary/summary` | 普通页 | 遛完总结 |
| 4 狗狗主页 | dog-detail | `pages/dog-detail/dog-detail` | 普通页 | `?id=` |
| 5 遛狗历史 | history | `pages/history/history` | 普通页 | 含补记弹层 |
| 6/6b 记录 | records | `pages/records/records` | **Tab** | 含空状态 |
| 7 完整成就 | achievements | `pages/achievements/achievements` | 普通页 | `?dogId=`,两入口共用 |
## 2. tabBar 配置app.json
底部三 Tab中央「遛狗」为凸起按钮。微信原生 tabBar 不支持凸起 FAB因此采用 **自定义 tabBar**`custom: true` + `components/custom-tab-bar/`)实现原型的橙色凸起按钮和状态变色。
```jsonc
{
"tabBar": {
"custom": true, // 自定义,实现中央凸起 FAB
"list": [
{ "pagePath": "pages/records/records", "text": "记录" },
{ "pagePath": "pages/home/home", "text": "我的" }
// 中央“遛狗”由自定义 tabBar 渲染,非真实 tab 页
]
}
}
```
中央「遛狗」按钮行为:
- 点击 → 单狗直接进 `walking`;多狗弹 `multi-dog-sheet` 选择后再进。
- 遛狗中时按钮变绿显示「遛狗中」,点击回到 `walking`;暂停态变黄「已暂停」。
## 3. 路由跳转表
```
login ──(新用户)──► dog-edit ──完成──► home(tab)
login ──(老用户)──────────────────► home(tab)
home ──点狗卡片──► dog-detail
home ──点「成就」──► achievements
home ──中央按钮──┬─(单狗)─► walking
└─(多狗)─► multi-dog-sheet ─选定─► walking
walking ⇄ (暂停态同页切换)
walking ──结束+二次确认──► summary ──完成──► dog-detail / home
walking ──定位/数据降级──► (未授权时隐藏距离·狗步;授权时累计 GPS 距离并换算狗步)
dog-detail ──右上编辑──► dog-edit?id=xxx预填
dog-detail ──近期记录──► summary只读回看占位后续替换为 record-detail
records(tab) ──成就概览/全部──► achievements
records(tab) ──+补记──► manual-add-sheet
records(tab) ──历史项──► summary只读回看占位后续替换为 record-detail
history ──右上+──► manual-add-sheet ──保存──► 刷新历史
```
## 4. 公共组件拆分
| 组件 | 用于页面 | 职责 |
|------|---------|------|
| `custom-tab-bar` | 全局 | 三 Tab + 中央状态化 FAB |
| `dog-card` | home | 狗狗卡片(连续天数、犬种) |
| `stat-grid` | summary, dog-detail | 三宫格统计 |
| `walk-row` | history, records, dog-detail | 一条遛狗记录(含补记标识) |
| `achievement-medal` | dog-detail, records, achievements | 勋章(解锁/锁定/进度) |
| `multi-dog-sheet` | tabBar 触发 | 多狗选择弹层 |
| `manual-add-sheet` | history, records | 补记表单弹层 |
| `chip-group` | dog-edit, summary | 单选标签组(性别/年龄/天气/状态) |
## 5. 关键交互细节(迁移自原型注释)
- **遛狗中计时**:基于 `startAt` 实时差值计算,扣除累计暂停时长;切后台不丢(见架构文档 §4
- **定位增强**:不在首次打开强制索权;开始遛狗后可请求定位。授权时累计 GPS 距离并按狗狗 `stride` 换算狗步,未授权时隐藏距离/狗步,只保留时长。
- **便便打卡**:行内 toggle遛狗中即时写入会话状态结束时随 walk 落库。
- **暂停态**:停止计时、数据置灰,主按钮变「继续」——防止等狗/捡便便污染数据。
- **结束**:必须二次确认弹窗,再进成果卡。
- **成果卡视角**:文案「豆豆今天走了 3,480 狗步」,非「你遛了」。
- **空状态6b**新用户「记录」Tab 显俏皮引导 +「带豆豆出门」按钮,成就墙全灰待解锁;遛完第一次切正常态。
- **分享**:成果卡「生成图片分享朋友圈」用 Canvas 2D 绘制成果图,是拉新主入口。
- **历史回看**V1 可复用 `summary` 做只读态,但不得显示「完成」这类结束流程按钮;正式形态后续补 `record-detail`

69
docs/05-业务规则.md Normal file
View File

@@ -0,0 +1,69 @@
# 05 · 业务规则(必落地)
> 这些规则散落在 PRD 与原型的橙色虚线注释里,是产品的「魂」。开发时务必逐条实现,不可省略。
## R1 · 登录与建档分流
- 微信授权登录后:**无任何狗狗 → 直接进建档0b**;已有狗狗 → 跳过,进首页。
- 协议/隐私为必须合规要素,登录页底部展示。
## R2 · 建档门槛最低
-**名字、犬种** 必填,其余(性别/年龄/体重/头像)全选填——降低首次门槛。
- 建档与编辑**复用同一表单**;编辑时预填已有值,标题改「编辑资料」。
- 犬种用于映射体型档位;中华田园犬、串串、找不到的犬种必须让用户手动选体型档位。
## R3 · 狗步算法
- 狗步公式:`dogStepsByDog[dogId] = round(distanceMeters / dog.stride)`
- `stride` 由体型档位决定,不做「每品种一个系数」。体型档位与基础步幅:超小型 `0.20m`、小型/短腿 `0.25m`、中型 `0.35m`、大型 `0.45m`、超大型 `0.55m`
- 填了体重时,在基础步幅上做二次校准:`stride = baseStride * (1 + (weight - 档位标准体重) * 0.005)`,修正幅度封顶 ±15%。
- 狗步只是展示层:多狗同遛时按每只狗分别计算;成就、未来排行榜和防刷统计只用距离、时长、连续天数等客观量。
## R4 · 多狗选择
- 仅多狗用户点遛狗时弹出选择层;单狗直接进遛狗中。
- 选择层**可多选**,支持多只狗一起遛(一条 walk 关联多个 dogId
## R5 · 遛狗计时与暂停
- 计时基于时间戳差值,扣除暂停时长;**切后台/锁屏不丢**。
- **暂停 = 停止计时 + 数据置灰**,主按钮变「继续」。意义:遛狗常有等狗、捡便便的停顿,暂停防止污染数据。
- 结束必须**二次确认**再生成成果卡。
## R6 · 定位与数据降级
- V1 纳入可选 GPS 距离与狗步:授权时累计距离并换算狗步。
- 未授权定位时,**距离 / 狗步隐藏**(存 null时长照常。不阻断遛狗闭环。
- 首次打开不强制索权;定位应在开始遛狗或用户主动开启增强能力时请求。
- 地图轨迹回放、精准轨迹去噪后置,不阻塞 V1。
## R7 · 成果卡全选填
- 照片、备注、天气、状态等所有输入字段**全部选填,跳过也能保存**——遛狗时用户腾不出手。
- 视角为「狗狗走了」而非「你遛了」。
- **分享朋友圈是首选 CTA**(拉新入口)。
- 核心记录在结束遛狗时先保存;成果卡补充字段通过回写接口保存,跳过不影响记录沉淀。
## R8 · 手动补记
- 入口:历史页 / 记录 Tab 右上「+」。
- 数据层标记 `source='manual'``isManual=true`
- **补记可维持连续打卡不断签**(参与连续天数计算)。
- **补记不计入排行榜**`countsForRanking=false`,防刷量)。
- 补记记录列表带「补记」标识区分。
## R9 · 统计维度
- 累计只用 **次数 / 公里 / 连续天数**
- **不要配速、不要卡路里**——记录陪伴厚度,而非运动表现。
## R10 · 连续天数算法
-`walkDate`(本地时区 yyyy-mm-dd为单位。
- 同日多次遛狗:连续天数不变。
- 距上次为「昨天」:`currentStreak += 1`
- 距上次 > 1 天(断签):重置为 1。
- 补记同样按其 `walkDate` 参与此计算。
## R11 · 成就体系
- 成就**按狗维度**,基于 **连续 / 累计 / 场景**,而非「最多 / 最快」。
- 未解锁项显示进度(「还差 X」强化收集与坚持动机。
- 成就墙露灰锁制造收集欲;新用户也显示全灰待解锁(含「首遛」)。
- 「记录」Tab 与「我的→成就」两入口指向**同一页**,文案统一叫「成就」。
## R12 · 安全与防刷
- 所有写操作 + 统计累加 + 成就解锁**只在云函数执行**,客户端集合权限禁写。
- openid 一律服务端取,不信任客户端传参。
- 同一 `(dogId, achievementKey)` 不重复解锁。

145
docs/06-开发计划.md Normal file
View File

@@ -0,0 +1,145 @@
# 06 · 开发计划
按「先跑通核心闭环,再做沉淀与增长」的顺序。每阶段可独立验收。
---
## ✅ 进度总览(截至 2026-06-25
V1 开发主体(阶段 0 → 4全部完成并部署。
**环境**`cloud1-d0gmb6bjde8af3266` · AppId `wx6cfaffb865048e83` · 部署方式 CloudBase CLI`tcb`,配置见根目录 `cloudbaserc.json`)。
**已部署云函数7**`login``dogManage``walkSave``walkManual``getDogDetail``getHistory``sendReminder`+ 每日 19:00 定时触发器 `dailyReminder`)。
**数据库集合4已建**`users``dogs``walks``dog_achievements`
**页面9全部真实功能**login / dog-edit / home / walking / summary / dog-detail / history / records / achievements组件custom-tab-bar、manual-add-sheet。
| 阶段 | 状态 |
|------|------|
| 0 工程搭建 | ✅ 完成 |
| 1 账号与建档闭环 | ✅ 完成 |
| 2 遛狗核心闭环 | ✅ 完成 |
| 2.5 定位与狗步 | ✅ 完成 |
| 3 沉淀与激励 | ✅ 完成 |
| 4 增长 | ✅ 完成(订阅消息待填模板 ID |
| 5 Pending | ⏸ 未启动(不在 V1 |
---
## 🐛 逻辑漏洞修正2026-06-25 代码审查)
对照 [05-业务规则](./05-业务规则.md) 审查现网代码,发现并修正以下逻辑漏洞:
### 1. 补记无法维持连续打卡(违反 R8 / R10
- **问题**`walkSave`/`walkManual` 的连续天数只依据 `lastWalkDate + currentStreak` 做增量推算。当用户「先实时遛了今天 → 再补记昨天」时,补记日期早于 `lastWalkDate`,算法走 `diff<=0` 分支原样返回,**连续天数不会向前补全**,直接违背 R8「补记可维持连续打卡不断签」。
- **修正**:新增 `computeStreak(openid, dogId)`,落库后**从该狗全部 `walkDate` 历史重算**连续天数(`Set` 去重同日多次、以最新日期为锚向前数连续日、遇断签停止)。补记以任意顺序插入都能正确续签/断签。`walkSave``walkManual` 均改用此函数,`lastWalkDate` 也取自重算结果。
### 2. 「早起鸟」成就永不可解锁(违反 R11
- **问题**`utils/achievements.js` 定义了 6 个成就且 `getDogDetail` 也按 6 统计,但「早起鸟」(早遛 10 次) **在任何云函数中都没有解锁逻辑、也无计数字段**,导致用户永远停在 5/6成就墙存在一个不可达成项。
- **修正**`walkSave`(仅实时遛狗,场景类不计补记)按开始时间本地小时 `<7` 累加 `stats.earlyWalks`,达 10 次解锁;`STAT_ACHIEVEMENTS` 增加 `early_bird` 判定。前端 `achievements.js` 进度与「还差 X 次」文案改为基于 `earlyWalks` 真实计算。
### 3. 成果卡选填字段「先分享后补填」会丢失(违反 R7
- **问题**`summary.js``ensureAttached` 在字段为空时即把 `_attached``true`。若用户先点「分享」(此时未填)再补填天气/备注/照片后点「完成」,回写被提前锁定跳过,**选填字段与「雨天战士」成就一并丢失**。
- **修正**:空内容时仅 `return` 不置位,只有真正成功回写后才锁定 `_attached`,保证后续补填仍能写入。
> 数据兼容:旧狗档无 `earlyWalks` 字段,云函数与前端均以 `|| 0` 兜底;连续天数在下一次遛狗/补记时按历史自动重算修正,无需迁移。
---
**待你手动处理**
1. 订阅消息模板 ID — 公众平台创建后填入 `utils/config.js``sendReminder/index.js`,重新部署。
2. (可选)数据库索引与权限收紧 — 见 [02-数据模型](./02-数据模型.md) §5/§6当前客户端不直读库默认管理端权限不影响功能。
3. (可选)存量旧狗数据迁移 — 阶段1建的狗无 `sizeLevel/stride`进编辑页重存即可补齐walkSave 已兜底 0.35m 不报错)。
---
## 阶段 0 · 工程搭建(地基)
**目标**:能在微信开发者工具里跑起空壳,云开发环境就绪。
- [x] 创建小程序 + 开通云开发,建环境(实际 env `cloud1-d0gmb6bjde8af3266`)。
- [x] 初始化目录结构(见 [01-架构设计](./01-架构设计.md))。
- [x] `app.js` 初始化 `wx.cloud``app.wxss` 落地设计 token。
- [x] 纯手写基础 UI不引第三方库全局 `.btn/.card` 等 + 组件 `custom-tab-bar`/`manual-add-sheet``dog-card`/`stat-grid` 等暂内联在页面,未抽独立组件)。
- [x] 建 4 个数据库集合CLI 建好)。 ⚠️ 索引未建、权限为默认管理端(客户端不直读,暂不影响)—— 见 [02-数据模型](./02-数据模型.md) §5/§6。
- [x] `utils/cloud.js` 封装 callFunction统一错误/loading
**验收**:✅ 工程可在开发者工具导入运行;云函数经 `tcb fn deploy` 部署可调通。
---
## 阶段 1 · 账号与建档闭环
**对应屏**0a 登录 → 0b 建档 → 1 我的
- [x] `login` 云函数 + 登录页R1 分流)。
- [x] `dogManage` 云函数create/update/listR2
- [x] 建档/编辑页(复用表单,犬种 pickerchip 单选)。
- [x] 首页:用户卡 + 狗狗卡 + 功能入口列表。
- [x] 自定义 tabBar中央状态化 FABidle/walking/paused 三态变色)。
**验收**:✅ 新用户登录→建档→进首页看到自己的狗;编辑能预填回写。
---
## 阶段 2 · 遛狗核心闭环V1 重点)⭐
**对应屏**2/2b 遛狗中 → 3 成果卡 → 4 狗狗主页
- [x] 遛狗会话状态机walking/paused时间戳计时后台恢复R5
- [x] 多狗选择弹层R4
- [x] 便便打卡、暂停/继续、结束二次确认R5
- [x] 定位增强 + 降级处理R6授权时累计 GPS 距离并换算狗步;未授权时距离/狗步存 null 且界面隐藏。
- 狗步模型按 R3 落地体型档位→步幅→体重±15%校准(`utils/dogStep.js` + `dogManage`);未知犬种建档强制选体型档位。
- `walkSave``action:'finish'`、收 `distanceMeters`、按每只狗 stride 算狗步walk 增 `source` 字段。
- [x] `walkSave` 云函数:落库 + stats 更新 + 连续天数R10+ 成就判定R11`finish/attach` 动作回写选填字段 + 雨天战士。
- [x] 成果卡页(全选填 R7新成就提示三宫格统计
- [x] `getDogDetail` + 狗狗主页(累计 R9、成就墙、近期记录
**验收**:完整走通「点遛狗→计时/距离/狗步(定位授权时)→结束→成果卡→记录沉淀进狗狗主页」;未授权定位时只保留时长,连续天数与统计正确累加。
---
## 阶段 3 · 沉淀与激励
**对应屏**5 历史 → 6/6b 记录 → 7 成就
- [x] 遛狗历史页(按周分组,补记标识)。
- [x] `walkManual` 补记云函数 + 补记弹层组件R8
- [x] 新增 `getHistory` 云函数(跨全部狗狗的历史列表,附狗名)。
- [x] 记录 Tab成就概览 + 历史;空状态 6b 引导)。
- [x] 完整成就页(已解锁/未解锁含进度,两入口共用)。
- [x] 成就定义表 `utils/achievements.js` 全量落地。
**验收**:补记不计排行但维持连续;成就解锁与进度展示正确;新用户空状态引导生效。
---
## 阶段 4 · 增长
- [x] 成果卡 Canvas 生成分享图(`canvas type=2d``showShareImageMenu`+ `onShareAppMessage/onShareTimeline` 转发。
- [x] 头像/照片云存储上传与压缩(`utils/media.js`chooseMedia 压缩 → cloud.uploadFile头像在首页/狗狗主页/成果卡显示。
- [x] 连续打卡订阅消息:`sendReminder` 云函数 + 每日 19:00 定时触发器;成果卡「完成」时 `requestSubscribeMessage`
- ⚠️ 需手动:公众平台创建订阅消息模板 → 把模板 ID 填入 `miniprogram/utils/config.js``cloudfunctions/sendReminder/index.js`,并按模板字段改 `data`。未配置时该功能静默跳过。
**验收**:成果卡能生成带数据的分享图并分享;头像/照片可上传并展示;模板配置后定时提醒可发。
---
## 阶段 5 · 后置 / Pending不在 V1
- 地图轨迹回放、GPS 去噪与更精细的轨迹纠偏。
- 广场、附近、排行榜、信息站。
- 排行榜需新增定时聚合云函数,按 `countsForRanking=true` 统计。
---
## 里程碑建议
| 里程碑 | 含阶段 | 可交付 | 状态 |
|--------|--------|--------|------|
| **M1 可建档** | 0 + 1 | 登录建档跑通 | ✅ 达成 |
| **M2 可遛狗** ⭐ | 2 + 2.5 | 核心闭环可用(含 GPS/狗步),可内测 | ✅ 达成 |
| **M3 可沉淀** | 3 | 完整 V1 体验 | ✅ 达成 |
| **M4 可传播** | 4 | 上线 + 拉新 | ✅ 达成(订阅消息待填模板 ID |
> 下一步建议:真机全流程联调 → 处理上方「待你手动」三项 → 提审上线。阶段 5排行榜/广场等)需先做产品设计再排期。

View File

@@ -0,0 +1,42 @@
# 07 · PRD 对齐评审
> 目的:逐条对比 `wangquan-PRD.md` 与 `docs/` 落地设计,判断哪一边方案更合理,并记录已经写回设计文档的结论。
## 结论总览
整体方向以 PRD 为产品边界,以 `docs/` 为工程落地方式。PRD 在产品范围、狗步算法、定位降级、拟人化表达上更完整;`docs/` 在技术选型、云函数拆分、安全、防刷、后台计时恢复上更落地。最终方案采用「PRD 定边界docs 定实现」的组合。
## 逐条对比
| 项 | PRD 方案 | docs 原方案 | 哪边更好 | 最终修改 |
|---|---|---|---|---|
| V1 范围 | V1 包含计时、距离、狗步;轨迹可后置 | 计时先跑通GPS 距离/狗步后置 | PRD 更好。距离/狗步是成果卡拟人化的关键,不应整体后置 | V1 纳入可选 GPS 距离与狗步后置仅保留轨迹回放、GPS 去噪 |
| 定位权限 | 首版不强制定位,未授权隐藏距离/狗步 | 降级处理有,但把距离/狗步存 null 作为阶段方案 | PRD 更清晰 | 明确“授权则增强,未授权则降级”,首次打开不强制索权 |
| 狗步算法 | 体型档位决定步幅,体重可二次校准 | `breedFactor` 犬种系数 | PRD 更好。犬种系数会把模型复杂化,也不利于串串/未知犬种 | 改为 `sizeLevel/baseStride/stride`,未知犬种手动选体型 |
| 多狗同遛狗步 | 支持多狗一起遛,但 PRD 未展开字段结构 | 单个 `dogSteps` 字段 | 组合后更好。多狗同遛时每只狗步幅不同,不能只存一个狗步 | 改为 `dogStepsByDog: { [dogId]: steps }`,服务端按每只狗 `stride` 计算 |
| 狗步用途 | 狗步只是展示层,不用于跨狗比较、成就、排行 | 有 `totalSteps`,但约束不够明确 | PRD 更好 | 文档补充:成就/排行只用距离、时长、连续天数等客观量 |
| 登录建档分流 | 登录后按是否已有狗判断进入建档或首页 | `login` 返回 `hasDog` | docs 更落地 | 保留 `hasDog` 云函数方案 |
| 建档字段 | 名字、犬种必填,其余选填 | 同 PRD | 一致 | 保留,补充未知犬种体型选择 |
| 计时与暂停 | 暂停停止计时,结束二次确认 | 时间戳差值 + `pausedTotal`,支持后台恢复 | docs 更好 | 保留 docs 的时间戳实现PRD 规则不变 |
| 成果卡补充字段 | 照片/文字/天气/心情集中成果卡,全部选填 | `walkSave` 入参混在结束保存里,计划里提到 attach 但设计不完整 | PRD 交互更好docs 的 attach 思路更落地 | `walkSave` 明确拆成 `finish``attach` 两个动作 |
| 成果卡分享 | 分享是首选 CTA分享图样式建议尽早做 | Canvas 分享图在增长阶段 | docs 更务实 | 保留增长阶段,但成果卡仍保留分享 CTA |
| 狗狗主页统计 | 总次数/总公里/连续天数,不要配速/卡路里 | 同 PRD | 一致 | 保留 |
| 记录 Tab | 成就概览 + 历史 + 补记;空状态露出灰锁 | 同 PRD | 一致 | 保留 |
| 手动补记 | 可维持连续,不计未来排行榜,标记数据来源 | `isManual` + `countsForRanking` | 组合后更好 | 增加 `source='manual'/'realtime'`,保留 `isManual` 便于展示 |
| 连续天数算法 | 补记可不断签,以日期判断 | docs 写了同日/昨日/断签逻辑 | docs 更落地 | 保留 docs 算法 |
| 成就体系 | 按连续/累计/场景,不按最多 | 同 PRD且有 `dog_achievements` 集合 | docs 更落地 | 保留集合设计,强调按狗维度 |
| 单次记录详情页 | 建议补,只读详情页更合理;首版可暂用成果卡只读态 | 历史项直接跳 summary 回看 | PRD 更完整docs 更省实现 | V1 允许 `summary` 只读占位,但禁止出现“完成”等流程按钮;后续补 `record-detail` |
| 设置页 | PRD 建议补定位权限、关于、隐私政策 | docs 未纳入页面清单 | PRD 更完整,但不阻塞 V1 主闭环 | 暂不扩页面清单,作为后续待补项处理 |
| 技术选型 | PRD 不限定 | 原生小程序 + 云开发 | docs 更好 | 保留 |
| 安全与防刷 | 需要区分实时/补记,防排行刷量 | 所有写操作走云函数openid 服务端取,客户端禁写 | docs 更好 | 保留并补充 `source` 字段 |
| Pending 功能 | 社区、附近、排行榜、信息站、商城不进首版 | 同 PRD | 一致 | 保留 |
## 已写回的文档
- `README.md`:更新 V1 范围和文档索引。
- `01-架构设计.md`:补充定位会话字段,修正 `dogStep.js` 职责。
- `02-数据模型.md`:改为体型档位/步幅模型,新增 `source``dogStepsByDog`
- `03-云函数设计.md`:补齐 `dogManage` 步幅计算、`walkSave finish/attach`、服务端狗步计算。
- `04-页面与路由.md`:明确定位增强、历史回看只读占位。
- `05-业务规则.md`:重新整理必落地规则,新增狗步算法和定位规则。
- `06-开发计划.md`:把 V1 距离/狗步拉回核心闭环,后置项改为轨迹回放与去噪。

37
docs/README.md Normal file
View File

@@ -0,0 +1,37 @@
# 汪圈 · 遛狗小程序 — 设计文档
> 微信原生小程序 + 云开发CloudBase。V1 聚焦「遛狗单机闭环」:登录 → 建档 → 首页 → 遛狗 → 成果卡 → 沉淀。
## 一句话定位
记录「陪伴的厚度」而非运动表现。视角是「狗狗走了」而非「你遛了」。用 **连续 / 累计 / 成就** 沉淀情感,用 **成果卡分享朋友圈** 做拉新。
## 技术栈
| 层 | 选型 |
|----|------|
| 客户端 | 微信原生小程序WXML / WXSS / JS |
| UI 组件库 | 不用第三方库,纯手写 WXSS最贴合原型视觉、包体最小 |
| 后端 | 微信云开发:云数据库 + 云存储 + 云函数 |
| 鉴权 | 云调用 `cloud.getWXContext().OPENID`(免自建 token |
| 状态管理 | 轻量全局 store`app.globalData` + 事件总线,不引 Redux |
## 文档索引
| 文档 | 内容 |
|------|------|
| [01-架构设计.md](./01-架构设计.md) | 技术选型、工程目录、全局状态、设计 token |
| [02-数据模型.md](./02-数据模型.md) | 云数据库集合设计、索引、权限 |
| [03-云函数设计.md](./03-云函数设计.md) | 云函数清单、入参出参、职责划分 |
| [04-页面与路由.md](./04-页面与路由.md) | 页面清单(对应原型 12 屏)、路由表、组件拆分 |
| [05-业务规则.md](./05-业务规则.md) | 关键业务规则(必落地)汇总 |
| [06-开发计划.md](./06-开发计划.md) | 里程碑与阶段拆分 |
| [07-PRD对齐评审.md](./07-PRD对齐评审.md) | PRD 与落地设计逐条对比、取舍结论 |
## 范围边界V1
- ✅ 纳入:登录、建档、遛狗计时闭环、可选 GPS 距离、狗步换算、成果卡、狗狗主页、历史、补记、成就。
- ⏸ 后置:地图轨迹回放、精准轨迹去噪、单次记录详情页完整形态。
- ❌ 不做pending广场、附近、排行榜、信息站。