# 汪圈小程序 — 功能逻辑缺陷审查报告

> 审查日期：2026-06-18
> 审查范围：全部 10 个页面、10 个 Service、23 个云函数、3 个类型文件、关键组件
> 审查分支：`master`

---

## 一、广场页 `pages/plaza/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **Feed 无分页 / 无限滚动** | 🔴 高 | `loadFeed()` (L34-38) | `getFeedList` 云函数返回 `CursorResponse<T>` 含 `nextCursor` 分页字段，但页面完全没有使用分页机制。只加载第一页数据，用户无法浏览更多帖子。Feed 是社交应用的核心功能，此为致命缺陷。 |
| 2 | **话题列表硬编码** | 🟡 中 | `topics` (L24) | `['全部', '滨江公园', '金毛日常', ...]` 写死在组件中，不会从云端动态获取热门话题，运营新增的话题无法展示。 |
| 3 | **广告卡片位置固定** | 🟡 中 | `PostCard` 渲染逻辑 (L130-133) | `FeedAdCard` 写死在 `index === 0` 的位置。每次 `loadFeed` 刷新后数据重置，广告位置永远在第一条帖子后，缺乏真实广告系统的随机插入和频次控制。 |
| 4 | **收藏功能存在但入口缺失** | 🟡 中 | `PostCard` + `PlazaPage` | `feed.service.ts` 导出了 `setPostLike` / `setPostFavorite`，`Post` 类型有 `favoritedByMe` 字段，但 `PostCard` 和 `PlazaPage` 均没有收藏操作的 UI 入口，收藏功能全链路断裂。 |
| 5 | **没有帖子详情页** | 🟡 中 | `commentPost()` (L86-113) | 评论通过 `Taro.showModal` 弹窗实现，无法查看评论列表、历史记录，也无法点击图片查看大图预览。社交类帖子的完整交互（评论链、转发、分享）均缺失。 |
| 6 | **点赞乐观更新存在竞态** | 🟡 中 | `likePost()` (L54-84) | 连续两次 `setPosts`（乐观更新 → 服务端返回覆盖），期间若 `useDidShow` 触发 `loadFeed`，可能覆盖掉刚点赞的中间状态。应考虑用 `postId` 粒度的状态管理或防抖。 |
| 7 | **搜索栏无功能** | 🟡 中 | `<SearchBar>` (L118) | `SearchBar` 组件已渲染但没有绑定 `onInput` / `onSubmit` 回调，搜索框为纯装饰。 |

---

## 二、附近页 `pages/nearby/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **没有调用 `updateLocation` 上报自身位置** | 🔴 高 | 整个页面 | `nearby.service.ts` 导出了 `updateLocation(location, visible)`，用户位置变化**从未上报云端**。其他用户看不到"我"在附近地图上出现，双向发现功能完全失效。应在获取到位置后调用上报，并在 relocate 时重复上报。 |
| 2 | **没有尊重 `preferences.nearbyVisible` 偏好** | 🔴 高 | 整个页面 | 用户在个人资料中可关闭"附近可见"(`preferences.nearbyVisible`)，但附近页完全不考虑此设置。即使关闭，仍请求并展示附近宠物，自身位置也没有根据偏好控制是否上报。应在 `useEffect` 中读取偏好，决定是否请求附近数据。 |
| 3 | **`likePet` 没有乐观更新和错误回滚** | 🟡 中 | `likePet()` (L89-92) | 只调用了 `likeNearbyPet(id)`，没有先更新 UI 状态（不像 `PlazaPage.likePost` 做了乐观更新 + 回滚）。网络慢时用户看不到反馈，快速点击可能重复请求。 |
| 4 | **`filter: 'online'` 后端过滤能力不确定** | 🟡 中 | `loadPets()` (L41-46) | 前端传 `{ type: 'online' }`，但 `nearbyPets` 云函数是否支持此过滤逻辑取决于后端实现。如果后端不识别此 filter，返回的数据未做二次前端过滤，展示可能不准确。 |
| 5 | **搜索仅前端过滤** | 🟡 中 | `visiblePets` (L139-142) | 只按 `name` 和 `breed` 过滤，不支持按距离排序或更丰富的筛选组合。筛选器仅有 all/dog/cat/online 四种，缺少按距离、年龄段、标签等维度。 |

---

## 三、发布页 `pages/publish/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **没有恢复草稿的逻辑** | 🔴 高 | `useEffect` (L44-50) | `publish.service.ts` 导出了 `getDraft()`，但发布页 `mount` 时**从未调用它**。用户保存的草稿在下次进入发布页时不会被恢复。应添加逻辑：进入时检测未提交内容 → 提示恢复草稿 → 调用 `getDraft()` 回填表单。 |
| 2 | **没有内容字数上限预警** | 🟡 中 | `ComposerToolbar` `count` prop | `isValidPostContent` 限制 1–2000 字，但 `content.length` 仅展示数字，没有在接近上限（如 >1800 字）时给出警告样式或禁用输入。 |
| 3 | **图片上传失败静默跳过** | 🟡 中 | `uploadPostMedia()` (`publish.service.ts` L24-38) | 单张图片上传失败时 `catch` 返回原 `item`，用户不知道哪张图没上传成功。帖子可能携带本地临时路径 `wxfile://...` 发布，其他用户无法查看。应在 UI 上展示上传失败提示。 |
| 4 | **返回没有未保存提示** | 🟡 中 | 关闭按钮 (L148-150) | 点击 `navigateBack()` 直接返回，如果用户已输入内容但未发布，不会提醒"是否保存为草稿"。应检测 `hasContent`，有内容时弹窗确认。 |

---

## 四、消息页 `pages/messages/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **`markConversationRead` 是 fire-and-forget** | 🟡 中 | `openConversation()` (L48-54) | 调用了 `markConversationRead` 但没有 `await`，且 `openConversation` 是 `async` 函数却没有 `try-catch`。如果 `navigateTo` 失败（如页面栈满），异常不会被捕获。 |
| 2 | **没有区分 single / system 会话交互** | 🟡 中 | `openConversation()` (L48-54) | `Conversation` 有 `type: 'single' \| 'system'`，系统消息（如"汪圈小助手"）不应有"发起聊天"行为，但 UI 上没有区分处理——点击系统消息也会跳转到 chat 页面。 |
| 3 | **没有消息通知 / 推送机制** | 🟡 中 | 整个页面 | 没有接入微信订阅消息或任何推送通知机制。`preferences.notifications` 偏好存在但无实际功能接入，用户即使打开通知设置也不会收到新消息提醒。 |
| 4 | **搜索不会过滤 activeUsers** | 🟡 中 | `kw` 过滤逻辑 (L64-69) | 搜索只过滤 `conversations`，不搜索 `activeUsers`。有搜索关键词时 `activeUsers` 被隐藏（`kw ? null : <ActiveUserRow>`），但清空搜索后立即恢复，交互上不连贯。 |

---

## 五、聊天页 `pages/chat/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **消息列表没有分页加载历史** | 🔴 高 | `load()` (L26-35) | 只调用一次 `getThread()` 获取当前消息，没有向上滚动加载更早消息的机制。长时间对话的历史消息无法查看。应实现 `ScrollView` 到顶触发加载上一页。 |
| 2 | **轮询频率固定 5 秒，无智能调节** | 🟡 中 | `useDidShow` (L43-45) | 不管页面是否在前台活跃、消息密度如何，都固定 5 秒轮询一次。高频轮询消耗资源，低频则延迟高。对于聊天场景，建议使用 WebSocket 或根据消息活跃度动态调整轮询间隔。 |
| 3 | **发送失败时输入框闪空** | 🟡 中 | `send()` (L53-71) | 发送时先 `setDraft('')` 清空输入框，失败后 `setDraft(text)` 恢复。在发送到失败的间隙，输入框会先空再恢复，视觉闪烁。建议先不清空，成功后再清空。 |
| 4 | **没有消息发送状态指示** | 🟡 中 | 聊天气泡 UI (L92-103) | `sending` 状态变量存在但没有用在 UI 上。用户发送消息后没有 loading / 发送中指示器，不知道消息是否正在发送。 |
| 5 | **`scrollTop` 递增 hack 不可靠** | 🟡 中 | `setScrollTop(prev => prev + 100000)` (L34) | 通过不断递增 `scrollTop` 强制滚动到底部。`Number.MAX_SAFE_INTEGER` 为 `9007199254740991`，每次 +100000 约可调用 9×10¹⁰ 次才溢出——虽然实际不太可能触发，但设计上应改用 ref 记录并 toggle 一个标志位。 |
| 6 | **不支持图片 / 表情消息** | 🟡 中 | 输入栏 UI (L108-119) | 只支持纯文本输入，没有图片、表情等富媒体消息功能。`ChatMessage.type` 字段存在但没有被使用，输入区域也没有附件按钮。 |

---

## 六、个人资料页 `pages/profile/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **偏好 `changePreference` 无错误回滚** | 🟡 中 | `changePreference()` (L92-102) | 先乐观更新 UI 再 `await updateProfile`，但整个函数没有 `try-catch`。如果云端更新失败，UI 状态已改但数据未持久化，下次加载会回滚，造成用户困惑（以为保存了但实际没有）。 |
| 2 | **"关于"功能是空壳** | 🟢 低 | `handleAction('about')` (L89) | 只弹 `toast: '敬请期待新功能'`。MVP 阶段可接受，但应在版本规划中补充。 |

---

## 七、资料编辑页 `pages/profile-edit/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **`type='nickname'` 仅真机生效** | 🟡 中 | `<Input type='nickname'>` (L116) | `type='nickname'` 是微信小程序获取微信昵称的特殊能力，仅在真机上有效。H5 预览 / 开发工具中用户可手动输入任意昵称（绕过微信身份验证）。应在非真机环境增加校验提示。 |
| 2 | **`avatarUrl` 与 `avatarKey` 共存冲突** | 🟡 中 | `submit()` (L56-82) | 编辑后 `uploadAvatar` 返回 `fileID` 赋给 `patch.avatarUrl`，但 `avatarKey` 没有被更新或清除。如果 `Avatar` 组件优先读 `avatarUrl`，则 `avatarKey` 多余；若优先读 `avatarKey`，可能显示旧头像。需要明确两者的优先级关系。 |
| 3 | **没有未保存离开提示** | 🟡 中 | 关闭按钮 (L88-90) | 直接 `navigateBack()` 没有检测是否有未保存修改。应比较当前表单值与初始值的差异，有差异时弹窗确认。 |

---

## 八、宠物编辑页 `pages/pet-edit/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **没有宠物数量上限限制** | 🟡 中 | `submit()` (L132-168) | 没有检查用户已添加的宠物数量。用户可以无限添加宠物，可能导致数据膨胀。建议在后端限制（如最多 5 只），前端也做预检提示。 |
| 2 | **照片隐式非必填但交互暗示必填** | 🟡 中 | `choosePhoto()` + 验证逻辑 | 提交验证只检查 `name` 和 `breed`，无照片也能保存。但展示逻辑依赖 `photoKey` 的兜底图，用户可能以为照片是必须的。建议明确标注照片为"可选"，或在空照片时展示更友好的占位。 |

---

## 九、联系人页 `pages/contacts/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **"发现"搜索需手动提交，不支持实时搜索** | 🟡 中 | `<Input onConfirm>` (L101-102) | 搜索需要点击键盘"搜索"按钮才触发 (`onConfirm`)，没有 `onInput` 实时触发搜索。对于"发现陌生人"场景，用户期望输入即搜索，应增加防抖实时搜索。 |
| 2 | **点击用户只能发起聊天，无个人主页入口** | 🟡 中 | `openChat()` (L63-67) | 点击用户头像/名称只能跳转到聊天页，没有查看对方个人主页（宠物列表、帖子等）的选项。用户想了解对方更多信息需要退出再找，交互断裂。 |
| 3 | **互关后 `isFriend` 未即时更新** | 🟡 中 | `onFollow()` (L51-61) | `toggleFollow` 返回 `{ mutual }` 但只更新了 `isFollowing`，没有同步更新 `isFriend`。互关后列表中不会立即显示"汪友"标签，需手动刷新页面才能看到。 |

---

## 十、我的动态页 `pages/my-posts/index.tsx`

| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **没有分页加载** | 🔴 高 | `load()` (L19-23) | 只加载一次 `getUserPosts()`，没有翻页。如果用户帖子很多，无法查看全部历史帖子。应实现 `ScrollView` 到底触发加载下一页。 |
| 2 | **`useDidShow` 首次触发导致重复加载** | 🔴 高 | `useDidShow(load)` (L31) | `useDidShow` 没有用 `firstShow` ref 跳过首次加载（对比 `PlazaPage` 和 `MessagesPage` 有此保护），首次进入页面时 `load()` 会被调用**两次**（`useEffect` + `useDidShow`），浪费一次网络请求。 |
| 3 | **`likePost` 没有错误回滚** | 🟡 中 | `likePost()` (L33-47) | 乐观更新后调用 `setPostLike`，但如果失败不会回滚到之前的状态。对比 `PlazaPage.likePost` 有 `previousPosts` 回滚逻辑，此处遗漏了。 |

---

## 十一、跨页面系统性问题

| # | 问题 | 影响范围 | 说明 |
|---|------|----------|------|
| 1 | **全应用没有全局登录态守卫** | 所有页面 | 所有 service 通过 `canUseCloud()` 判断是否可用，但没有统一的"未登录则拦截/跳转"逻辑。只有 `ProfilePage` 单独处理了登录态，其他页面（广场、附近、消息、发布等）均假定已登录，未登录用户可能看到空数据或报错。 |
| 2 | **`Post.favoritedByMe` 收藏能力全链路断裂** | PlazaPage, PostCard | 类型定义有 `favoritedByMe` 字段，`feed.service.ts` 导出 `setPostFavorite` 方法，但 `PostCard` 组件和 `PlazaPage` 均没有收藏操作的 UI 入口，前端从未调用过该 service 方法。 |
| 3 | **草稿系统断裂** | PublishPage | `draftSave` → `draftGet` 云函数完整存在，`publish.service.ts` 导出了 `getDraft()`，但发布页 `mount` 时从不调用它。草稿保存后永远无法恢复，草稿功能形同虚设。 |
| 4 | **`StatsRow` 数据可能与子页面不同步** | ProfilePage → ContactsPage, MyPostsPage | 个人资料页 `stats.posts/following/followers` 来自 `getProfile()`，跳转到 `contacts?tab=following` 时 `getFollowList()` 可能因缓存延迟返回不同计数，造成数字前后不一致。 |
| 5 | **没有全局错误边界** | 全应用 | 任何 service 层未处理的异常会直接在页面崩溃。没有 React ErrorBoundary 或全局 Toast 兜底机制，用户体验差。 |
| 6 | **`CloudFunctionName` 联合类型缺少 `commentCreate`** | feed.service.ts L43 | `createPostComment` 调用了 `callCloud('commentCreate', ...)`，但 `CloudFunctionName` 联合类型中是否包含 `commentCreate` 取决于 `cloud.ts` 的定义。如果未声明，TypeScript 不会报错但运行时可能失败。 |

---

## 十二、修复优先级建议

### P0 — 必须立即修复（影响核心功能）

1. **Feed / 我的动态分页** — 社交应用内容流的基础能力
2. **发布页草稿恢复** — `getDraft()` 已存在，只需在页面 mount 时调用
3. **附近页上报自身位置** — `updateLocation()` 已存在，只需在获取位置后调用

### P1 — 短期内修复（影响用户体验）

4. 我的动态页 `useDidShow` 重复加载 → 添加 `firstShow` ref
5. 我的动态页 `likePost` 错误回滚
6. 附近页 `likePet` 添加乐观更新
7. 附近页尊重 `nearbyVisible` 偏好
8. 聊天页消息分页加载
9. 个人资料页 `changePreference` 添加 `try-catch`
10. 收藏功能 UI 入口补全

### P2 — 中期迭代（完善产品体验）

11. 搜索栏功能实现（广场页）
12. 联系人页实时搜索
13. 聊天页发送状态指示 + 发送失败 UX
14. 未保存离开提示（发布页、编辑页）
15. 全局登录态守卫
16. 全局错误边界
17. 消息通知 / 推送接入

### P3 — 长期优化（性能和扩展性）

18. 话题列表动态化
19. 广告系统正规化
20. 图片 / 富媒体消息
21. 帖子详情页 + 评论列表
22. 聊天页轮询策略优化（WebSocket）
23. 宠物数量上限
24. 用户个人主页入口（从联系人页）

---

> 本报告基于源码静态审查生成，所有问题均指向代码层面的逻辑缺陷，不涉及 UI/UX 视觉设计层面的评审。
