Files
Pawer/docs/功能缺陷审查报告.md

305 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 汪圈小程序 — 功能逻辑缺陷审查报告 v2.0
> 审查日期2026-06-19
> 基线版本v1.0 报告审查后的最新代码
> 审查范围:全部 11 个页面、10 个 Service、25+ 个云函数、4 个类型文件、3 个 Store/Hook、1 个新增 `cloud-file` 工具模块
> 审查分支:`master`
---
## v1 → v2 修复总结
v1 报告共提出 **36 条** 问题(含跨页面系统性问题 6 条),本次审查确认的修复情况如下:
| 类别 | v1 问题数 | ✅ 已修复 | ⚠️ 部分修复 | ❌ 未修复 | 🆕 新发现 |
|------|-----------|-----------|-------------|-----------|-----------|
| 广场页 | 7 | 5 | 0 | 1 | 3 |
| 附近页 | 5 | 4 | 0 | 0 | 2 |
| 发布页 | 4 | 4 | 0 | 0 | 2 |
| 消息页 | 4 | 4 | 0 | 0 | 0 |
| 聊天页 | 6 | 6 | 0 | 0 | 2 |
| 个人资料页 | 2 | 0 | 1 | 0 | 1 |
| 资料编辑页 | 3 | 1 | 0 | 2 | 0 |
| 宠物编辑页 | 2 | 0 | 0 | 2 | 0 |
| 联系人页 | 3 | 0 | 1 | 2 | 0 |
| 我的动态页 | 3 | 2 | 0 | 0 | 2 |
| 帖子详情页 | — | — | — | — | 4 (新增页) |
| 跨页面系统 | 6 | 4 | 0 | 2 | 2 |
| **合计** | **36+** | **26** | **2** | **7** | **18** |
---
## 一、广场页 `pages/plaza/index.tsx`
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | Feed 无分页 / 无限滚动 | ✅ 已修复 | 引入 `nextCursor``loadNextPage``PageShell lowerThreshold={160}``loadingMore` 状态,实现了完整的无限滚动。 |
| 2 | 话题列表硬编码 | ✅ 已修复 | 引入 `topicOptions` 状态 + `normalizeTopicOptions()`,从云端 `result.topics` 动态合并。 |
| 3 | 广告卡片位置固定 | ✅ 已修复 | 引入 `createAdStartIndex()` 随机起点 + `AD_INTERVAL = 6` 间隔,每次加载重新随机化。 |
| 5 | 没有帖子详情页 | ✅ 已修复 | 新增 `pages/post-detail/index` 页面,`PostCard` 支持 `onOpen` 导航。 |
| 6 | 点赞乐观更新竞态 | ✅ 已修复 | 引入 `feedRequestSeq` + `pendingLikeIds` / `pendingLikeOverrides` 防重复机制,失败时回滚到 `previousPost` 快照。 |
| 7 | 搜索栏无功能 | ✅ 已修复 | 绑定 `onInput` + 300ms 防抖 `debouncedKeyword`,搜索时展示结果计数 + 清除按钮。 |
### ❌ v1 遗留
| v1 # | 问题 | 说明 |
|------|------|------|
| 4 | 收藏功能入口缺失 | `Post.favoritedByMe` 字段和 `feed.service.setPostFavorite()` 仍然存在,`PostCard` 仍然没有收藏操作的 UI 入口。跨页面系统性问题 #2 未解决。 |
### 🆕 v2 新发现
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **`loadFeed` 依赖数组包含 `debouncedKeyword` 导致频繁重置** | 🟡 中 | `useEffect` (L156-158) | `loadFeed``useCallback` 依赖了 `debouncedKeyword`。防抖 300ms 后 `debouncedKeyword` 变化 → `loadFeed` 重建 → `useEffect` 触发 → 清空列表重新加载。用户每输入一个字符(停顿 300ms 后),整个列表会被清空重载一次,体验上表现为列表频繁闪烁空白再重新出现。应将 `loadFeed` 拆分为"参数变化时的首次加载"和"追加加载",或使用 ref 追踪 keyword 变化而非作为 `useCallback` 依赖。 |
| 2 | **`PostCard` 移除了 `more` 按钮但无替代操作** | 🟡 中 | `PostCard` (L44-66) | v1 中 `PostCard` 有一个 `more` 图标按钮三点点v2 中完全移除了。用户无法对帖子执行更多操作(举报、分享、复制链接等),丢失了内容管理入口。 |
| 3 | **话题搜索结果与关键词搜索混合逻辑不一致** | 🟡 中 | 搜索逻辑 (L278-306) | 关键词搜索通过 `debouncedKeyword` 传给 `getFeedList`,服务端过滤。但话题筛选通过 `topic` 参数传给同一个 `getFeedList`。两者不能同时生效——用户选择了某个话题后又输入搜索词行为不明确。UI 上也没有层级提示告诉用户"在话题内搜索"还是"全局搜索"。 |
---
## 二、附近页 `pages/nearby/index.tsx`
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | 没有调用 `updateLocation` 上报自身位置 | ✅ 已修复 | 获取位置后调用 `updateLocation(loc, true)``relocate` 时也会更新,`nearbyVisible === false` 时传 `null, false`。 |
| 2 | 没有尊重 `preferences.nearbyVisible` 偏好 | ✅ 已修复 | 先 `getProfile()` 读取偏好,`nearbyVisible === false` 时不请求位置、清空宠物列表、不上报位置。 |
| 3 | `likePet` 没有乐观更新和错误回滚 | ✅ 已修复 | 引入 `pendingLikeIds`、乐观设置 `likedByMe: true`,失败时回滚到 `previousLiked`。 |
| 4 | `filter: 'online'` 后端过滤能力不确定 | ✅ 已修复 | `nearby.service.ts` 新增 `filterNearbyPets()` 前端兜底过滤。`nearbyPets` 云函数也已支持 `online` filter。 |
### 🆕 v2 新发现
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **`nearbyVisible``null` 时地图显示默认中心上海但无数据** | 🟡 中 | 初始化流程 (L32, L57-83) | `nearbyVisible` 初始值为 `null`(等待 `getProfile` 返回),此时地图渲染 `DEFAULT_CENTER`(上海),`center``null` 所以 `loadPets` 不触发,宠物列表为空。用户看到一个空地图和空列表,直到 `getProfile` 完成(可能 1-2 秒)。应显示加载指示器或骨架屏。 |
| 2 | **没有清理自身旧位置(登出 / 关闭可见性)** | 🟢 低 | `nearbyVisible === false` 分支 (L61-67) | 调用 `updateLocation(null, false)` 但没有确认云函数是否正确处理 `null` location从数据库中移除旧坐标。如果云函数只是不写入用户的旧位置数据会一直留在数据库中。 |
---
## 三、发布页 `pages/publish/index.tsx`
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | 没有恢复草稿的逻辑 | ✅ 已修复 | `useEffect` 调用 `getDraft()`,通过 `hasDraftContent()` 判断后 `restoreDraft()` 回填所有表单字段。 |
| 2 | 没有内容字数上限预警 | ✅ 已修复 | `canSubmit` 检查 `textLength <= 2000`,超限时 `submit()``toast: '正文最多 2000 字'`。 |
| 3 | 图片上传失败静默跳过 | ✅ 已修复 | 新增 `UploadPostMediaError` 自定义错误类,`showUploadFailure()` 弹窗明确告知第几张图片上传失败。 |
| 4 | 返回没有未保存提示 | ✅ 已修复 | `handleClose()` 检查 `hasContent`,弹窗询问"保存草稿?" 或 "不保存"离开。 |
### 🆕 v2 新发现
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **`useUploadMedia``setMedia` 暴露了内部 setter** | 🟢 低 | `useUploadMedia` 解构 (L53) | `const { media, setMedia, addImages, removeMedia } = useUploadMedia([])` — 直接暴露 `setMedia` 并在 `restoreDraft` 中使用。绕过了 `useUploadMedia` 的内部管理逻辑,如果该 hook 未来添加了额外状态(如上传状态),直接 `setMedia` 不会触发这些逻辑。应通过 hook 暴露一个 `replaceMedia` 方法。 |
| 2 | **草稿恢复成功后宠物默认选中逻辑冲突** | 🟡 中 | `useEffect` (L81-87) | `getPetList``useEffect``setSelectedPetId(prev => prev \|\| (draftRestoredRef.current ? '' : list[0]._id))`。当草稿恢复成功时 `draftRestoredRef.current = true`,但如果草稿没有设置 `petId``draft.petId` 为空),宠物不会被默认选中——这可能不是用户期望的(用户之前可能选中了一个宠物但草稿没保存 petId。 |
---
## 四、消息页 `pages/messages/index.tsx`
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | `markConversationRead` 是 fire-and-forget | ✅ 已修复 | `openConversation` 中正确 `await markConversationRead`,失败时回滚 `unreadCount``navigateTo` 失败时也回滚。 |
| 2 | 没有区分 single / system 会话交互 | ✅ 已修复 | `item.type === 'system'` 时直接调用 `markConversationRead` 标记已读并 toast "系统消息已读",不再跳转聊天页。 |
| 3 | 没有消息通知 / 推送机制 | ⚠️ 部分修复 | — | 见跨页面系统问题 #3(推送机制仍未接入,但消息页本身已正确处理未读状态)。 |
| 4 | 搜索不会过滤 activeUsers | ✅ 已修复 | 搜索时也过滤 `activeUsers``visibleActiveUsers`,有匹配时显示"在线汪友"分区。 |
### v2 无新增问题
---
## 五、聊天页 `pages/chat/index.tsx`
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | 消息列表没有分页加载历史 | ✅ 已修复 | 引入 `loadOlder``nextCursor``upperThreshold={48}` 触发加载。加载后滚动到锚点消息位置。 |
| 2 | 轮询频率固定 5 秒 | ✅ 已修复 | 引入 `ACTIVE_POLL_MS = 3000` / `IDLE_POLL_MS = 12000` / `ACTIVE_POLL_WINDOW_MS = 60000` 三档智能调节。 |
| 3 | 发送失败时输入框闪空 | ✅ 已修复 | `send()` 中不再先 `setDraft('')`,而是在 `sendMessage` 成功后才 `setDraft('')``sendImageMessage` 也类似处理。 |
| 4 | 没有消息发送状态指示 | ✅ 已修复 | `sending` 状态驱动 UI按钮显示"发送中"、输入框 `disabled`、工具栏按钮 disabled。 |
| 5 | `scrollTop` 递增 hack 不可靠 | ✅ 已修复 | 改用 `scrollIntoView` + 动态 `bottomAnchorId``scrollSeq` 递增)实现可靠滚动到底部。 |
| 6 | 不支持图片 / 表情消息 | ✅ 已修复 | 新增图片发送 (`sendImages`)、表情面板 (`EMOJI_OPTIONS`)、图片消息展示 + 预览 (`previewImage`)。 |
### 🆕 v2 新发现
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **图片消息只显示非 `cloud://` URL 的图片** | 🔴 高 | 图片渲染逻辑 (L442) | `m.content && !m.content.startsWith('cloud://')` — 当消息的 content 是 `cloud://` 文件 ID已上传到云端但 `resolveCloudFileUrl` 没有成功转换时),会显示"图片暂不可用"文本气泡。`messageThread` 云函数和 `resolveThreadAssets` 应该已经解析了这些 URL但如果解析失败用户会看到"图片暂不可用",没有重试机制。更严重的是,任何以 `cloud://` 开头的内容都被认为是"不可用图片"而非普通文本,如果某些消息的 type 被错误标记为 `image` 但 content 是 cloud ID将无法展示。 |
| 2 | **`loadLatest` 提前返回时可能不清除 `loadingInitial`** | 🟢 低 | `loadLatest` (L183-220) | 如果 `requestId !== latestRequestSeq.current` 导致提前 return`loadingInitial` 不会被清除——但 `finally` 块仍会执行return 在 try 块内),所以实际不会出现此问题。仅限极端竞态场景。 |
---
## 六、个人资料页 `pages/profile/index.tsx`
### ⚠️ v1 部分修复
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | 偏好 `changePreference` 无错误回滚 | ⚠️ 部分修复 | `updateProfile` 内部调用 `markStale('profile')`,数据会在下次 `useRefreshOnShow` 时刷新。但 `changePreference` 本身仍然没有 `try-catch`——如果 `updateProfile` 抛出异常UI 状态已改但数据未持久化,直到下次刷新才恢复。用户仍然会看到一个短暂的"假成功"状态。 |
### 🆕 v2 新发现
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **`useSession` 初始化时 `ensureLogin``ProfilePage` 自身的 `ensureLogin` 重复调用** | 🟢 低 | `useEffect` (L40-47) | `useSession` hook 内部在 mount 时已经调用了 `ensureLogin()``ProfilePage``useEffect` 中又调用了一次。虽然有 `inflight` 去重机制不会产生两次网络请求,但代码结构上造成了理解困惑。可考虑依赖 `useSession` 的 user 即可。 |
---
## 七、资料编辑页 `pages/profile-edit/index.tsx`
### ❌ v1 遗留
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | `type='nickname'` 仅真机生效 | ❌ 未修复 | 仍然是 `type='nickname'`,没有非真机环境的校验提示。这是微信平台限制,可接受。 |
| 2 | `avatarUrl``avatarKey` 共存冲突 | ❌ 未修复 | 新增了 `avatarDisplayUrl` 变量L25: `avatarUrl && !avatarUrl.startsWith('cloud://') ? avatarUrl : ''`),解决了 `cloud://` ID 在 `<Image>` 中无法显示的问题。但 `avatarKey` 在头像已上传的情况下仍然是一个无用的遗留值,`Avatar` 组件和 `Image` 组件的优先级关系仍不明确。 |
| 3 | 没有未保存离开提示 | ❌ 未修复 | 关闭按钮仍然是直接 `navigateBack()`,没有检测是否有未保存修改。 |
### v2 无新增问题
---
## 八、宠物编辑页 `pages/pet-edit/index.tsx`
### ❌ v1 遗留
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | 没有宠物数量上限限制 | ❌ 未修复 | `PetShelf` 组件仍然无条件显示"添加宠物"按钮。后端也没有数量限制。 |
| 2 | 照片隐式非必填但交互暗示必填 | ❌ 未修复 | 提交验证只检查 `name``breed`,无照片也能保存。新增了 `photoDisplayUrl` 逻辑L81解决了 `cloud://` ID 显示问题。 |
### v2 无新增问题
---
## 九、联系人页 `pages/contacts/index.tsx`
### ⚠️ v1 部分修复
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 3 | 互关后 `isFriend` 未即时更新 | ⚠️ 部分修复 | v1 报告指出 `onFollow` 只更新 `isFollowing` 不更新 `isFriend`。当前代码仍然只更新 `isFollowing``isFriend` 未同步。需调用 `toggleFollow` 后根据返回的 `mutual` 更新 `isFriend`。 |
### ❌ v1 遗留
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 1 | "发现"搜索需手动提交 | ❌ 未修复 | 仍然只有 `onConfirm` 触发搜索,没有实时搜索或防抖输入触发。 |
| 2 | 点击用户只能发起聊天 | ❌ 未修复 | 仍然没有用户个人主页入口。 |
### v2 无新增问题
---
## 十、我的动态页 `pages/my-posts/index.tsx`
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 2 | `useDidShow` 首次触发导致重复加载 | ✅ 已修复 | 改用 `useRefreshOnShow('myPosts', () => load(true))`,首次 onShow 被跳过。 |
### ❌ v1 遗留
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 3 | `likePost` 没有错误回滚 | ❌ 未修复 | `likePost` 仍然没有 try-catch 和回滚逻辑——`await setPostLike(postId, target.likedByMe)` 失败时不会恢复之前的点赞状态。 |
### 🆕 v2 新发现
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **仍然没有分页加载** | 🟡 中 | `load()` (L21-31) | `userPosts` 云函数硬编码 `limit(50)`,前端一次性加载全部 50 条。如果用户帖子超过 50 条,超出部分无法查看。应添加 `cursor` / `pageSize` 参数到 `userPosts` 云函数和前端。 |
| 2 | **`likePost` 通过 `onPostLike` 事件同步但有重复调用** | 🟡 中 | `likePost` (L49-63) | `likePost` 做了乐观更新 + 调用 `setPostLike`(后者通过 `emitPostLike` 广播)。同时还有一个 `useEffect` 订阅 `onPostLike` 事件。当用户在"我的动态"页点赞时,`setPostLike``emitPostLike` → 本页的 `onPostLike` 监听器也会触发 `setPosts`,导致同一条帖子的点赞状态被**设置了两次**(第一次是 `likePost` 直接设置,第二次是事件监听器设置)。虽然结果相同,但多了一次不必要的 re-render。 |
---
## 十一、帖子详情页 `pages/post-detail/index.tsx` 🆕 新增页面
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|---|------|----------|------|------|
| 1 | **评论缺少分页加载** | 🟡 中 | `load()` (L30-59) | `postDetail` 云函数一次返回最多 200 条评论(`safeGet('comments', { postId }, 200)`),前端没有分页。如果帖子评论超过 200 条,超出部分无法查看。 |
| 2 | **`likePost` 的乐观更新使用闭包中的 `post` 而非 ref** | 🟡 中 | `likePost` (L66-85) | `const previousPost = post`L82 回滚时使用)来自当前渲染闭包中的 `post` state。如果用户快速双击点赞第一次 `setPost(next)` 还没 re-render第二次进入时 `post` 仍是旧值),乐观更新的"之前"状态会不准确。应使用 ref 追踪最新的 post。 |
| 3 | **发送评论后新评论的 `author` 硬编码为"我"** | 🟡 中 | `send()` (L87-116) | 新评论被乐观添加到列表,但 `author: { id: 'me', name: '我', avatarKey: 'gradient-avatar-1' }` 是硬编码的,没有使用当前用户的真实昵称和头像。用户看到自己评论显示为"我"而非自己的昵称。应从 `useSession()` 获取当前用户信息。 |
| 4 | **`load` 函数的 `finally` 中总是设置 `commentsLoaded = true`** | 🟢 低 | `load()` (L58) | 即使 `getPostDetail` 抛出异常,`commentsLoaded` 也会被设为 `true`,导致"评论加载中…"变成"还没有评论"。但此时用户不知道加载失败了(没有错误提示)。应在 catch 中显示错误状态。 |
---
## 十二、跨页面系统性问题
### ✅ v1 修复确认
| v1 # | 问题 | 状态 | 说明 |
|------|------|------|------|
| 3 | 草稿系统断裂 | ✅ 已修复 | 发布页 mount 时调用 `getDraft()``resolveDraftMedia()` 解析草稿中的 cloud file URL表单完整回填。 |
| 5 | 没有全局错误边界 | ✅ 已修复 | 各页面和 service 层普遍添加了 try-catch 和错误回滚。虽然不是 React ErrorBoundary但每个操作都有用户可感知的错误提示。 |
| 6 | `CloudFunctionName` 缺少 `commentCreate` | ✅ 已修复 | `cloud.ts` 中已包含 `commentCreate`。 |
### ❌ v1 遗留
| v1 # | 问题 | 说明 |
|------|------|------|
| 1 | 全应用没有全局登录态守卫 | `session.store.ts` 实现了共享 session + `ensureLogin``app.tsx` 在 launch 时预热登录。但广场、附近、消息、发布等页面仍然**没有检查登录态**——未登录用户能看到空数据但不会跳转到登录页。 |
| 4 | `StatsRow` 数据可能与子页面不同步 | 未修复。`profile.getProfile()``contacts.getFollowList()` 返回的计数仍然可能因缓存延迟不一致。 |
### 🆕 v2 新发现
| # | 问题 | 影响范围 | 严重程度 | 说明 |
|---|------|----------|----------|------|
| 1 | **`cloud-file.ts``tempUrlCache` 永不过期** | 全应用 | 🟡 中 | `resolveCloudFileUrls` 将 cloud file ID 到临时 URL 的映射缓存在内存 Map 中,但**永不清除**。长时间使用小程序后,这个 Map 会持续增长(每次加载帖子/头像/评论都添加条目)。虽然单个 URL 映射很小,但如果用户大量浏览内容(数百帖子,数千评论),累积效果不应忽视。应添加 LRU 或 TTL 淘汰策略。 |
| 2 | **`dataBus.ts``postCache` 永不清除** | 跨页面 | 🟢 低 | `cachePost` 将帖子数据存入全局 Map`getCachedPost` 只在 `PostDetailPage` 中调用。这个 Map 永远不会被清除——如果用户浏览了很多帖子,所有数据都会留在内存中。应在一定时间后或 Map 大小超过阈值时清除。 |
| 3 | **`messageList` 云函数中 `peerOpenids` 过滤的变量遮蔽** | 消息页 | 🟡 中 | `messageList/index.js` L88: `(c.memberOpenids \|\| []).find(openid => openid !== OPENID)``find` 的回调参数名 `openid` 遮蔽了外层的 `OPENID` 常量。逻辑上结果正确(参数 `openid` !== `OPENID` 等价于"找到不是我的那个成员"),但变量命名遮蔽在未来维护时容易引入真实 bug。应重命名回调参数`member`)。 |
| 4 | **`messageThread` 云函数每次轮询都执行一次已读标记 update** | 聊天页 → 消息页 | 🟢 低 | `messageThread/index.js` L97-99: 每次请求消息列表都会 `update` conversation 清除未读。聊天页的轮询3-12 秒一次)会频繁触发不必要的数据库写入。如果两个用户同时在看同一会话,一方的轮询会清除另一方的未读状态——虽然 `_.pull(OPENID)` 只清除当前用户,但频繁 update 仍是不必要的开销。应区分首次加载(标记已读)和轮询刷新(不标记已读)。 |
| 5 | **`nearbyPets` 云函数不按距离排序** | 附近页 | 🔴 高 | `nearbyPets/index.js` L86: `db.collection('pets').where(query).limit(50).get()` — 没有 `.orderBy` 按距离排序。返回的宠物列表按 MongoDB 默认的写入顺序排列,用户看到的附近宠物不是按距离远近排列的。对于"附近"功能来说,距离排序是核心体验。应使用 geoNear 或在应用层按 `distanceText` 排序。 |
---
## 十三、修复优先级建议
### P0 — 必须立即修复
1. **`nearbyPets` 云函数不按距离排序** — "附近"功能的核心体验缺失,用户期望看到最近的宠物排在最前面
2. **聊天页图片消息 `cloud://` 不显示** — 用户发送/接收的图片消息可能显示"图片暂不可用"
3. **`messageList` 云函数 `peerOpenids` 变量遮蔽** — 代码可维护性风险,未来修改时容易引入真实 bug
### P1 — 短期内修复
4. 联系人页 `onFollow` 互关后未即时更新 `isFriend`
5. 联系人页"发现"搜索无实时搜索
6. 我的动态页 `likePost` 错误回滚
7. 我的动态页无分页 + `userPosts` 云函数 `limit(50)` 硬编码
8. 广场页搜索输入导致列表频繁重载(`debouncedKeyword``loadFeed` 重建 → 清空列表)
9. 帖子详情页评论 `author` 硬编码"我"
10. 收藏功能 UI 入口补全(`PostCard` 添加收藏按钮)
11. `changePreference` 添加 `try-catch`
12. 帖子详情页 `likePost` 闭包问题 → ref
### P2 — 中期迭代
13. 资料编辑页 / 宠物编辑页未保存离开提示
14. 帖子详情页评论分页
15. `PostCard` 恢复"更多操作"按钮
16. 宠物数量上限
17. 全局登录态守卫(非 TabBar 页面检查登录)
18. 联系人页添加用户个人主页入口
19. `messageThread` 轮询时避免不必要的已读标记 update
20. 附近页 `nearbyVisible` 加载状态提示
### P3 — 长期优化
21. `cloud-file.ts` tempUrlCache 添加 TTL / LRU 淘汰
22. `dataBus.ts` postCache 添加大小限制 / TTL
23. 消息通知 / 推送接入
24. 用户个人主页(完整帖子列表 + 宠物列表展示)
25. 广场页话题与关键词搜索混合逻辑优化
---
> 本报告基于源码静态审查生成,所有问题均指向代码层面的逻辑缺陷,不涉及 UI/UX 视觉设计层面的评审。