修复动态发布与互动问题
This commit is contained in:
309
docs/功能缺陷审查报告.md
309
docs/功能缺陷审查报告.md
@@ -1,170 +1,303 @@
|
||||
# 汪圈小程序 — 功能逻辑缺陷审查报告
|
||||
# 汪圈小程序 — 功能逻辑缺陷审查报告 v2.0
|
||||
|
||||
> 审查日期:2026-06-18
|
||||
> 审查范围:全部 10 个页面、10 个 Service、23 个云函数、3 个类型文件、关键组件
|
||||
> 审查日期: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 | **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` 回调,搜索框为纯装饰。 |
|
||||
| 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 | **没有调用 `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 四种,缺少按距离、年龄段、标签等维度。 |
|
||||
| 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 | **没有恢复草稿的逻辑** | 🔴 高 | `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`,有内容时弹窗确认。 |
|
||||
| 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`
|
||||
|
||||
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|
||||
|---|------|----------|------|------|
|
||||
| 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>`),但清空搜索后立即恢复,交互上不连贯。 |
|
||||
### ✅ 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 | **消息列表没有分页加载历史** | 🔴 高 | `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` 字段存在但没有被使用,输入区域也没有附件按钮。 |
|
||||
| 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 | **偏好 `changePreference` 无错误回滚** | 🟡 中 | `changePreference()` (L92-102) | 先乐观更新 UI 再 `await updateProfile`,但整个函数没有 `try-catch`。如果云端更新失败,UI 状态已改但数据未持久化,下次加载会回滚,造成用户困惑(以为保存了但实际没有)。 |
|
||||
| 2 | **"关于"功能是空壳** | 🟢 低 | `handleAction('about')` (L89) | 只弹 `toast: '敬请期待新功能'`。MVP 阶段可接受,但应在版本规划中补充。 |
|
||||
| 1 | **`useSession` 初始化时 `ensureLogin` 与 `ProfilePage` 自身的 `ensureLogin` 重复调用** | 🟢 低 | `useEffect` (L40-47) | `useSession` hook 内部在 mount 时已经调用了 `ensureLogin()`,`ProfilePage` 在 `useEffect` 中又调用了一次。虽然有 `inflight` 去重机制不会产生两次网络请求,但代码结构上造成了理解困惑。可考虑依赖 `useSession` 的 user 即可。 |
|
||||
|
||||
---
|
||||
|
||||
## 七、资料编辑页 `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()` 没有检测是否有未保存修改。应比较当前表单值与初始值的差异,有差异时弹窗确认。 |
|
||||
### ❌ 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`
|
||||
|
||||
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|
||||
|---|------|----------|------|------|
|
||||
| 1 | **没有宠物数量上限限制** | 🟡 中 | `submit()` (L132-168) | 没有检查用户已添加的宠物数量。用户可以无限添加宠物,可能导致数据膨胀。建议在后端限制(如最多 5 只),前端也做预检提示。 |
|
||||
| 2 | **照片隐式非必填但交互暗示必填** | 🟡 中 | `choosePhoto()` + 验证逻辑 | 提交验证只检查 `name` 和 `breed`,无照片也能保存。但展示逻辑依赖 `photoKey` 的兜底图,用户可能以为照片是必须的。建议明确标注照片为"可选",或在空照片时展示更友好的占位。 |
|
||||
### ❌ v1 遗留
|
||||
|
||||
| v1 # | 问题 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | 没有宠物数量上限限制 | ❌ 未修复 | `PetShelf` 组件仍然无条件显示"添加宠物"按钮。后端也没有数量限制。 |
|
||||
| 2 | 照片隐式非必填但交互暗示必填 | ❌ 未修复 | 提交验证只检查 `name` 和 `breed`,无照片也能保存。新增了 `photoDisplayUrl` 逻辑(L81),解决了 `cloud://` ID 显示问题。 |
|
||||
|
||||
### v2 无新增问题
|
||||
|
||||
---
|
||||
|
||||
## 九、联系人页 `pages/contacts/index.tsx`
|
||||
|
||||
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|
||||
|---|------|----------|------|------|
|
||||
| 1 | **"发现"搜索需手动提交,不支持实时搜索** | 🟡 中 | `<Input onConfirm>` (L101-102) | 搜索需要点击键盘"搜索"按钮才触发 (`onConfirm`),没有 `onInput` 实时触发搜索。对于"发现陌生人"场景,用户期望输入即搜索,应增加防抖实时搜索。 |
|
||||
| 2 | **点击用户只能发起聊天,无个人主页入口** | 🟡 中 | `openChat()` (L63-67) | 点击用户头像/名称只能跳转到聊天页,没有查看对方个人主页(宠物列表、帖子等)的选项。用户想了解对方更多信息需要退出再找,交互断裂。 |
|
||||
| 3 | **互关后 `isFriend` 未即时更新** | 🟡 中 | `onFollow()` (L51-61) | `toggleFollow` 返回 `{ mutual }` 但只更新了 `isFollowing`,没有同步更新 `isFriend`。互关后列表中不会立即显示"汪友"标签,需手动刷新页面才能看到。 |
|
||||
### ⚠️ 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()` (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 | **仍然没有分页加载** | 🟡 中 | `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 | **全应用没有全局登录态守卫** | 所有页面 | 所有 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 不会报错但运行时可能失败。 |
|
||||
| # | 缺陷 | 严重程度 | 位置 | 说明 |
|
||||
|---|------|----------|------|------|
|
||||
| 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 中显示错误状态。 |
|
||||
|
||||
---
|
||||
|
||||
## 十二、修复优先级建议
|
||||
## 十二、跨页面系统性问题
|
||||
|
||||
### P0 — 必须立即修复(影响核心功能)
|
||||
### ✅ v1 修复确认
|
||||
|
||||
1. **Feed / 我的动态分页** — 社交应用内容流的基础能力
|
||||
2. **发布页草稿恢复** — `getDraft()` 已存在,只需在页面 mount 时调用
|
||||
3. **附近页上报自身位置** — `updateLocation()` 已存在,只需在获取位置后调用
|
||||
| v1 # | 问题 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 3 | 草稿系统断裂 | ✅ 已修复 | 发布页 mount 时调用 `getDraft()`,`resolveDraftMedia()` 解析草稿中的 cloud file URL,表单完整回填。 |
|
||||
| 5 | 没有全局错误边界 | ✅ 已修复 | 各页面和 service 层普遍添加了 try-catch 和错误回滚。虽然不是 React ErrorBoundary,但每个操作都有用户可感知的错误提示。 |
|
||||
| 6 | `CloudFunctionName` 缺少 `commentCreate` | ✅ 已修复 | `cloud.ts` 中已包含 `commentCreate`。 |
|
||||
|
||||
### P1 — 短期内修复(影响用户体验)
|
||||
### ❌ v1 遗留
|
||||
|
||||
4. 我的动态页 `useDidShow` 重复加载 → 添加 `firstShow` ref
|
||||
5. 我的动态页 `likePost` 错误回滚
|
||||
6. 附近页 `likePet` 添加乐观更新
|
||||
7. 附近页尊重 `nearbyVisible` 偏好
|
||||
8. 聊天页消息分页加载
|
||||
9. 个人资料页 `changePreference` 添加 `try-catch`
|
||||
10. 收藏功能 UI 入口补全
|
||||
| v1 # | 问题 | 说明 |
|
||||
|------|------|------|
|
||||
| 1 | 全应用没有全局登录态守卫 | `session.store.ts` 实现了共享 session + `ensureLogin`,`app.tsx` 在 launch 时预热登录。但广场、附近、消息、发布等页面仍然**没有检查登录态**——未登录用户能看到空数据但不会跳转到登录页。 |
|
||||
| 4 | `StatsRow` 数据可能与子页面不同步 | 未修复。`profile.getProfile()` 和 `contacts.getFollowList()` 返回的计数仍然可能因缓存延迟不一致。 |
|
||||
|
||||
### P2 — 中期迭代(完善产品体验)
|
||||
### 🆕 v2 新发现
|
||||
|
||||
11. 搜索栏功能实现(广场页)
|
||||
12. 联系人页实时搜索
|
||||
13. 聊天页发送状态指示 + 发送失败 UX
|
||||
14. 未保存离开提示(发布页、编辑页)
|
||||
15. 全局登录态守卫
|
||||
16. 全局错误边界
|
||||
17. 消息通知 / 推送接入
|
||||
| # | 问题 | 影响范围 | 严重程度 | 说明 |
|
||||
|---|------|----------|----------|------|
|
||||
| 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` 排序。 |
|
||||
|
||||
### P3 — 长期优化(性能和扩展性)
|
||||
---
|
||||
|
||||
18. 话题列表动态化
|
||||
19. 广告系统正规化
|
||||
20. 图片 / 富媒体消息
|
||||
21. 帖子详情页 + 评论列表
|
||||
22. 聊天页轮询策略优化(WebSocket)
|
||||
23. 宠物数量上限
|
||||
24. 用户个人主页入口(从联系人页)
|
||||
## 十三、修复优先级建议
|
||||
|
||||
### 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. 广场页话题与关键词搜索混合逻辑优化
|
||||
|
||||
---
|
||||
|
||||
|
||||
343
docs/功能缺陷核验与修复设计.md
Normal file
343
docs/功能缺陷核验与修复设计.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# 汪圈小程序 - 功能缺陷核验与修复设计
|
||||
|
||||
> 核验日期:2026-06-20
|
||||
> 来源报告:`docs/功能缺陷审查报告.md`
|
||||
> 核验基线:当前工作区源码,重点核验 `src` 与 `cloudfunctions`,忽略 `dist` 构建产物
|
||||
> 目标:逐项确认报告中仍被标为缺陷/风险的条目是否真实存在,并为需要修改的条目形成可落地设计
|
||||
|
||||
## 1. 总体结论
|
||||
|
||||
报告中的“已修复确认”条目,当前代码整体能支撑报告结论;本次未发现需要重新打开的已修复项。消息页、发布页、附近页、聊天页的主要 v1 修复点均能在当前实现中找到对应逻辑。
|
||||
|
||||
报告中仍被标为遗留、新发现或系统性问题的条目共核验 35 项:
|
||||
|
||||
| 结论 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| 成立 | 24 | 当前代码中确实存在对应行为或缺口 |
|
||||
| 部分成立 | 5 | 现象存在,但报告描述、严重程度或根因需要修正 |
|
||||
| 不成立 | 6 | 当前代码已解决或报告判断与实现不符 |
|
||||
|
||||
需要特别修正报告优先级:
|
||||
|
||||
- `nearbyPets` 云函数不按距离排序:不成立。当前 `cloudfunctions/nearbyPets/index.js` 会计算距离、筛选半径内位置,并按 `_dist` 升序排序。
|
||||
- `messageList` 变量遮蔽:成立,但只是维护性风险,不应列为 P0。
|
||||
- 聊天图片 `cloud://` 不显示:部分成立。服务层已经尝试解析 `cloud://` 为临时 URL;真实问题是解析失败后会丢失可重试的 fileId,页面只能显示“图片暂不可用”。
|
||||
- 当前没有阻断级 P0 缺陷;建议从 P1 的数据一致性、分页、错误回滚开始修。
|
||||
|
||||
## 2. 已修复条目复核摘要
|
||||
|
||||
| 页面/模块 | 报告已修复项 | 本次复核 |
|
||||
|-----------|--------------|----------|
|
||||
| 广场页 | 分页、动态话题、广告随机、详情页、点赞竞态、搜索入口 | 代码中存在 `nextCursor`、`normalizeTopicOptions`、`createAdStartIndex`、`post-detail` 导航、`pendingLikeIds`/`pendingLikeOverrides`、搜索防抖 |
|
||||
| 附近页 | 位置上报、尊重可见性、喜欢乐观更新、online 过滤 | 代码中存在 `updateLocation`、`nearbyVisible` 分支、`pendingLikeIds` 回滚、前后端 online 过滤 |
|
||||
| 发布页 | 草稿恢复、字数上限、上传失败提示、关闭前保存草稿提示 | 代码中存在 `getDraft`/`restoreDraft`、`textLength <= 2000`、`UploadPostMediaError`、`handleClose` 草稿弹窗 |
|
||||
| 消息页 | 已读等待、系统会话区分、搜索 activeUsers | 代码中 `openConversation` 会等待跳转/已读结果,系统消息不进聊天,搜索同时过滤在线汪友 |
|
||||
| 聊天页 | 历史分页、智能轮询、发送失败不清空、发送状态、可靠滚动、图片/表情 | 代码中存在 `loadOlder`、3s/12s 轮询、成功后清空输入、`sending` 状态、`scrollIntoView`、图片和表情发送 |
|
||||
| 我的动态 | 首次 onShow 重复加载 | `useRefreshOnShow` 会跳过首次 show |
|
||||
| 跨页面 | 草稿系统、操作错误提示、`commentCreate` 类型 | 当前代码已接入草稿解析、常见操作 try/catch、`CloudFunctionName` 包含 `commentCreate` |
|
||||
|
||||
## 3. 逐项核验清单
|
||||
|
||||
| # | 报告条目 | 核验结论 | 是否需要修改 | 设计归属 |
|
||||
|---|----------|----------|--------------|----------|
|
||||
| 1 | 广场收藏功能入口缺失 | 成立。`Post`/云函数有收藏字段与能力,但 `PostCard` 无入口,`feed.service.ts` 也没有封装 `setPostFavorite` | 需要 | D1 |
|
||||
| 2 | 广场 `loadFeed` 依赖 `debouncedKeyword` 导致频繁清空 | 部分成立。每次防抖关键词变化都会重载并清空列表;这是搜索行为的一部分,但会造成闪空体验 | 建议修改 | D4 |
|
||||
| 3 | `PostCard` 移除更多按钮 | 成立。当前只剩点赞/评论,无举报、分享等入口 | 暂缓,需产品确认更多菜单内容 | D6 |
|
||||
| 4 | 话题搜索与关键词不能同时生效 | 不成立。前端同时传 `topic` 和 `keyword`,云函数也会按 topic 查询后做关键词匹配 | 不修改,仅可优化提示文案 | D4 |
|
||||
| 5 | 附近页 `nearbyVisible=null` 时显示默认上海与空列表 | 成立。偏好加载期间页面会渲染默认中心与空状态 | 需要 | D4 |
|
||||
| 6 | 关闭附近可见不清理旧位置 | 不成立。`locationUpdate` 在 `visible=false` 时会写入 `location:null`、`geo:null`、`visible:false`,不会被附近查询命中 | 不修改 | - |
|
||||
| 7 | `useUploadMedia` 暴露内部 `setMedia` | 成立。发布页直接用 setter 恢复草稿 | 建议小改 | D4 |
|
||||
| 8 | 草稿恢复后宠物默认选中逻辑冲突 | 部分成立。当前逻辑会把“草稿无 petId”解释为“不关联宠物”,但无法区分历史缺字段与用户主动不关联 | 需要明确语义后修改 | D4 |
|
||||
| 9 | 聊天图片 `cloud://` 不显示 | 部分成立。正常路径会解析为临时 URL;解析失败时会丢失 fileId,无法重试 | 需要 | D3 |
|
||||
| 10 | `loadLatest` 提前返回不清 `loadingInitial` | 不成立。`return` 位于 `try` 内,`finally` 仍会执行 | 不修改 | - |
|
||||
| 11 | 个人页偏好开关无失败回滚 | 成立。`changePreference` 先改 UI,`await updateProfile` 无 catch | 需要 | D1 |
|
||||
| 12 | `useSession` 与个人页重复 `ensureLogin` | 成立但影响很低。store 有 inflight 去重,不会造成重复请求 | 暂不修改,可作为结构清理 | D6 |
|
||||
| 13 | 资料编辑 `type='nickname'` 仅真机生效 | 成立但属于微信平台限制 | 不按缺陷修,可补充提示 | D6 |
|
||||
| 14 | `avatarUrl` 与 `avatarKey` 共存冲突 | 不成立。当前是图片优先、渐变头像兜底;`getProfile` 已解析 cloud URL | 不修改 | - |
|
||||
| 15 | 资料编辑页无未保存离开提示 | 成立。关闭按钮直接 `navigateBack` | 需要 | D4 |
|
||||
| 16 | 宠物数量无上限 | 部分成立。当前确实无限制,但是否是缺陷取决于产品策略 | 需要产品确定上限后修改 | D6 |
|
||||
| 17 | 宠物照片交互暗示必填 | 不成立。当前无必填标识,校验也只要求名字和品种 | 不修改,可加“可选”文案 | - |
|
||||
| 18 | 联系人互关后 `isFriend` 未即时更新 | 成立。`toggleFollow` 返回 `mutual`,页面未使用 | 需要 | D1 |
|
||||
| 19 | 联系人“发现”搜索需手动提交 | 成立。`onInput` 只更新本地关键词,只有 `onConfirm` 请求 | 建议修改 | D1 |
|
||||
| 20 | 点击用户只能发起聊天,无个人主页 | 成立。当前没有用户主页页面 | 中期功能 | D6 |
|
||||
| 21 | 我的动态点赞无错误回滚 | 成立。`likePost` 无 try/catch | 需要 | D1 |
|
||||
| 22 | 我的动态无分页,云函数 `limit(50)` | 成立。`userPosts` 无 cursor,前端一次加载 | 需要 | D2 |
|
||||
| 23 | 我的动态点赞事件导致重复 setPosts | 成立但影响低。服务成功会广播,页面本地也已设置 | 可随点赞回滚一起收敛 | D1 |
|
||||
| 24 | 帖子详情评论缺少分页 | 成立。`postDetail` 一次最多 200 条评论 | 需要 | D2 |
|
||||
| 25 | 帖子详情点赞闭包竞态 | 成立。回滚用当前渲染闭包中的 `post` | 需要 | D1 |
|
||||
| 26 | 发送评论后作者硬编码“我” | 成立。乐观评论未使用当前用户资料 | 需要 | D1 |
|
||||
| 27 | 帖子详情加载失败也设 `commentsLoaded=true` | 成立。`load` 无 catch,失败后会显示空评论 | 需要 | D4 |
|
||||
| 28 | 全局登录态守卫缺失 | 成立。`logout` 是本地标记,非 profile 页面未统一拦截 | 需要设计为轻量会话门禁 | D5 |
|
||||
| 29 | `StatsRow` 计数可能不同步 | 部分成立。`profileGet` 已实时计算,但 follow/favorite 等操作后没有统一标记 profile 失效 | 需要 | D5 |
|
||||
| 30 | `cloud-file.ts` 临时 URL 缓存永不过期 | 成立 | 需要 | D5 |
|
||||
| 31 | `dataBus.ts` `postCache` 永不清除 | 成立 | 需要 | D5 |
|
||||
| 32 | `messageList` 中 `openid` 回调变量遮蔽 | 成立,但只是可维护性问题 | 顺手修 | D6 |
|
||||
| 33 | `messageThread` 每次轮询都写已读 | 成立。云函数每次取线程都会 update 会话已读 | 需要 | D3 |
|
||||
| 34 | `nearbyPets` 不按距离排序 | 不成立。当前会按距离排序 | 不修改 | - |
|
||||
| 35 | 消息通知/推送未接入 | 成立,但这是新功能,不是当前消息列表逻辑缺陷 | 长期迭代 | D6 |
|
||||
|
||||
## 4. 修复设计
|
||||
|
||||
### D1. 互动状态一致性与回滚
|
||||
|
||||
目标:统一点赞、收藏、关注、偏好开关、评论乐观更新的失败回滚与跨页面同步,优先解决用户可感知的数据错乱。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `src/services/feed.service.ts`
|
||||
- `src/services/social.service.ts`
|
||||
- `src/pages/my-posts/index.tsx`
|
||||
- `src/pages/post-detail/index.tsx`
|
||||
- `src/pages/profile/index.tsx`
|
||||
- `src/pages/contacts/index.tsx`
|
||||
- `src/features/feed/components/PostCard/index.tsx`
|
||||
- `cloudfunctions/postFavorite/index.js`
|
||||
|
||||
设计:
|
||||
|
||||
1. 收藏能力补齐:
|
||||
- 在 `feed.service.ts` 新增 `setPostFavorite(postId, favorited)`,调用 `postFavorite` 云函数。
|
||||
- `postFavorite` 云函数补齐缺集合兜底,避免新库第一次收藏失败。
|
||||
- `PostCard` 增加收藏按钮与数量展示,使用 `post.favoritedByMe` 和 `counts.favorites`。
|
||||
- 广场、我的动态、帖子详情统一实现乐观收藏、失败回滚、成功后写入服务端返回值。
|
||||
- 新增收藏同步事件或复用 dataBus 扩展,例如 `emitPostFavorite({ postId, favorited, favorites })`。
|
||||
|
||||
2. 我的动态点赞:
|
||||
- 引入 `postsRef` 和 `pendingLikeIds`,按广场页模式保存操作前快照。
|
||||
- `setPostLike` 失败时恢复 `likedByMe` 与 `counts.likes`。
|
||||
- `onPostLike` 监听中先比较目标 post 当前值,相同则返回原数组,减少重复 re-render。
|
||||
|
||||
3. 帖子详情点赞:
|
||||
- 新增 `postRef` 保存最新 post,`likePost` 以 ref 为准,不用渲染闭包中的 `post`。
|
||||
- 加 `pendingLikeIds` 防止同一帖子连续点击造成并发请求。
|
||||
- 失败时回滚到请求开始时的 snapshot。
|
||||
|
||||
4. 评论乐观作者:
|
||||
- `PostDetailPage` 使用 `useSession()` 获取当前用户。
|
||||
- 乐观评论 author 使用 `user._id`、`user.nickname`、`user.avatarKey`、`user.avatarUrl`。
|
||||
- `commentCreate` 可在后续返回完整 comment;短期先保持 `{ commentId, comments }` 返回结构不变。
|
||||
|
||||
5. 联系人关注:
|
||||
- `onFollow` 使用 `toggleFollow` 返回的 `following` 与 `mutual` 回写 `isFollowing`、`isFriend`。
|
||||
- 失败回滚到完整旧 user snapshot,而不是只反转 `isFollowing`。
|
||||
- `social.service.toggleFollow` 成功后调用 `markStale('profile')`,让资料页统计在返回时刷新。
|
||||
|
||||
6. 偏好开关:
|
||||
- `changePreference` 保存 previous user。
|
||||
- 乐观更新后 `try/catch` 调用 `updateProfile`;失败恢复 previous user 并 toast。
|
||||
- `updateProfile` 的 `markStale('profile')` 建议移动到云函数成功后,避免失败也标记新鲜/脏状态混乱。
|
||||
|
||||
验收:
|
||||
|
||||
- 断网或云函数失败时,点赞/收藏/关注/偏好开关都能回滚 UI。
|
||||
- 互关后联系人页立即显示“汪友”。
|
||||
- 新评论展示当前用户昵称和头像,不再硬编码“我”。
|
||||
- 同一帖子在广场、我的动态、详情页的点赞/收藏状态保持一致。
|
||||
|
||||
### D2. 列表与评论分页
|
||||
|
||||
目标:移除 50/200 条硬上限,保证长列表可继续加载。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `cloudfunctions/userPosts/index.js`
|
||||
- `cloudfunctions/postDetail/index.js`
|
||||
- `src/services/feed.service.ts`
|
||||
- `src/pages/my-posts/index.tsx`
|
||||
- `src/pages/post-detail/index.tsx`
|
||||
- `src/types/cloud.ts`
|
||||
|
||||
设计:
|
||||
|
||||
1. `userPosts` 分页:
|
||||
- 云函数入参新增 `cursor?: string`、`pageSize?: number`。
|
||||
- 按 `createdAt desc` 查询 `pageSize + 1` 条,cursor 使用上一页最后一条 `createdAt`。
|
||||
- 返回 `{ list, nextCursor }`。
|
||||
- `getUserPosts` 改为返回 `CursorResponse<Post>`,兼容 `authorId`。
|
||||
- `MyPostsPage` 增加 `nextCursor`、`loadingMore`、`loadMore`,在滚动到底部加载。
|
||||
|
||||
2. 评论分页:
|
||||
- `postDetail` 入参新增 `commentCursor?: string`、`commentPageSize?: number`。
|
||||
- 评论按 `createdAt asc` 返回,初始取最早一页;下一页使用 `createdAt > commentCursor`。
|
||||
- 返回 `{ post, comments, commentsNextCursor }`。
|
||||
- `PostDetailPage` 增加 `commentsNextCursor`、`loadingCommentsMore`、底部“加载更多评论”。
|
||||
- 为避免刷新帖子详情时重复替换图片,继续保留当前“只更新计数”的策略。
|
||||
|
||||
验收:
|
||||
|
||||
- 我的动态超过 50 条时可以继续加载。
|
||||
- 帖子评论超过单页限制时可以继续加载下一页。
|
||||
- 分页加载失败有 toast,当前列表不被清空。
|
||||
|
||||
### D3. 聊天图片可靠展示与已读写入控制
|
||||
|
||||
目标:图片临时 URL 解析失败后可重试;聊天轮询不再每次写数据库。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `src/services/message.service.ts`
|
||||
- `src/pages/chat/index.tsx`
|
||||
- `src/types/domain.ts`
|
||||
- `cloudfunctions/messageThread/index.js`
|
||||
|
||||
设计:
|
||||
|
||||
1. 图片消息数据结构:
|
||||
- `ChatMessage` 增加可选字段 `fileId?: string`、`assetState?: 'ready' | 'unresolved'`。
|
||||
- `resolveThreadAssets` 解析图片时保留原始 `cloud://` 到 `fileId`,`content` 存临时 URL;解析失败时 `content=''` 且 `assetState='unresolved'`。
|
||||
- 页面渲染 unresolved 图片时展示占位和“重试”操作。
|
||||
- 重试时调用 `resolveCloudFileUrl(fileId)`,成功后只更新该条消息。
|
||||
|
||||
2. 已读写入控制:
|
||||
- `messageThread` 入参新增 `markRead?: boolean`,默认 `true` 以兼容旧调用。
|
||||
- 首次打开会话、用户主动刷新时传 `markRead:true`。
|
||||
- 静默轮询 `loadLatest({ silent:true })` 和加载历史传 `markRead:false`。
|
||||
- 云函数仅在 `markRead !== false` 且会话存在时执行已读 update。
|
||||
|
||||
验收:
|
||||
|
||||
- 图片临时 URL 获取失败后,页面不会永久丢失 cloud fileId。
|
||||
- 静默轮询不再触发会话已读 update。
|
||||
- 首次进入聊天仍会清除当前用户未读数。
|
||||
|
||||
### D4. 页面加载、搜索与离开保护
|
||||
|
||||
目标:减少空白闪烁,补齐错误状态与未保存提示。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `src/pages/plaza/index.tsx`
|
||||
- `src/pages/nearby/index.tsx`
|
||||
- `src/features/nearby/components/NearbySheet/index.tsx`
|
||||
- `src/pages/post-detail/index.tsx`
|
||||
- `src/pages/profile-edit/index.tsx`
|
||||
- `src/hooks/useUploadMedia.ts`
|
||||
- `src/pages/publish/index.tsx`
|
||||
|
||||
设计:
|
||||
|
||||
1. 广场搜索:
|
||||
- 搜索参数变化时保留旧列表,设置 `searching=true`,新结果回来后替换。
|
||||
- 或仅在用户按确认键时清空列表,防抖输入走静默刷新。
|
||||
- 搜索结果文案增加当前 topic,例如“在 #遛弯 中搜索...”,解决语义提示问题。
|
||||
|
||||
2. 附近偏好加载:
|
||||
- 增加 `preferenceLoading = nearbyVisible === null`。
|
||||
- 偏好未返回前,地图可显示默认中心,但列表区展示“正在读取附近设置”,不展示“附近暂时没有汪友”。
|
||||
- `NearbySheet` 接收 `loading` 或 `state`,区分 loading、disabled、empty 三种状态。
|
||||
|
||||
3. 帖子详情加载失败:
|
||||
- `load` 增加 catch,设置 `commentsError`。
|
||||
- 失败时展示“评论加载失败,点击重试”,不显示“还没有评论”。
|
||||
- `finally` 只负责关闭 loading,不吞掉错误状态。
|
||||
|
||||
4. 资料编辑未保存提示:
|
||||
- 加 `initialRef` 保存首次加载后的表单快照。
|
||||
- 关闭按钮走 `handleClose`:dirty 时弹窗确认放弃/继续编辑。
|
||||
- 保存成功后更新 dirty 基线或直接返回。
|
||||
|
||||
5. 上传媒体 hook:
|
||||
- `useUploadMedia` 对外暴露 `replaceMedia(next)`,发布页草稿恢复改用该方法。
|
||||
- 保留内部 `setMedia` 私有,后续上传状态不会被外部绕过。
|
||||
|
||||
6. 草稿宠物语义:
|
||||
- 新草稿保存时增加 `petSelectionExplicit: boolean` 或将 `petId` 扩展为 `string | null`。
|
||||
- `petId=null` 表示用户明确选择“不关联宠物”;`petId` 缺失表示历史草稿/未选择,可默认第一只宠物。
|
||||
- 对旧草稿做兼容:没有该字段时默认第一只宠物,除非后续产品明确要求保持“不关联”。
|
||||
|
||||
验收:
|
||||
|
||||
- 广场搜索输入时不出现明显空白闪烁。
|
||||
- 附近页读取设置期间不显示错误的空列表语义。
|
||||
- 评论加载失败有可见错误和重试。
|
||||
- 编辑资料有未保存离开确认。
|
||||
- 草稿恢复不会误把历史缺字段理解为用户主动不关联。
|
||||
|
||||
### D5. 会话门禁、统计失效与缓存淘汰
|
||||
|
||||
目标:统一“本地登出/资料未完成”的门禁语义,并控制长期运行内存增长。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `src/store/session.store.ts`
|
||||
- `src/services/auth.service.ts`
|
||||
- `src/services/social.service.ts`
|
||||
- `src/services/feed.service.ts`
|
||||
- `src/services/pet.service.ts`
|
||||
- `src/store/dataBus.ts`
|
||||
- `src/services/cloud-file.ts`
|
||||
- `src/pages/home/index.tsx`
|
||||
|
||||
设计:
|
||||
|
||||
1. 轻量登录态守卫:
|
||||
- 新增 `useSessionGate({ requireCompleted?: boolean })` 或等价 helper。
|
||||
- 对发布、消息、联系人、附近等需要真实用户态的页面/操作,若 `isLoggedOut()` 或 `!user.profileCompleted`,引导到“我的”完成登录资料。
|
||||
- 广场可保持只读,但点赞、收藏、评论、关注等写操作必须先过 gate。
|
||||
- 由于微信 openid 是静默登录,本设计不把“未登录”理解为无 openid,而是“用户本地退出或资料未完成”。
|
||||
|
||||
2. 统计失效:
|
||||
- follow、favorite、post create/delete、pet save/delete 成功后统一 `markStale('profile')`。
|
||||
- 资料页返回时通过现有 `useTabRefresh('profile')` 获取实时 `profileGet` 统计。
|
||||
- 如后续新增联系人资源 key,可扩展 `ResourceKey` 为 `contacts`,用于关注列表自身失效。
|
||||
|
||||
3. 临时 URL 缓存:
|
||||
- `tempUrlCache` 从 `Map<string,string>` 改为 `Map<string,{ url:string; expiresAt:number; lastAccess:number }>`。
|
||||
- 默认 TTL 50 分钟,低于微信临时 URL 常见有效期。
|
||||
- 最大容量建议 500;超出时按 `lastAccess` 淘汰最旧项。
|
||||
- 每次 resolve 前执行轻量 prune。
|
||||
|
||||
4. 帖子 hand-off 缓存:
|
||||
- `postCache` 增加 TTL 10 分钟、最大容量 100。
|
||||
- `getCachedPost` 读取过期项时删除并返回 `undefined`。
|
||||
- `cachePost` 写入时执行容量淘汰。
|
||||
|
||||
验收:
|
||||
|
||||
- 本地退出后,写操作不会继续静默执行。
|
||||
- 关注/收藏后回到资料页,统计能刷新。
|
||||
- 长时间浏览大量帖子/图片后,缓存 Map 不会无界增长。
|
||||
|
||||
### D6. 中长期产品能力与低风险清理
|
||||
|
||||
这些条目成立或部分成立,但不建议混入第一批稳定性修复。
|
||||
|
||||
1. 更多菜单:
|
||||
- `PostCard` 预留 `onMore`。
|
||||
- 菜单项建议先做“分享”“复制内容/链接”“举报”。
|
||||
- 举报若上线,需要新增 `postReport` 云函数和审核字段,不只是前端按钮。
|
||||
|
||||
2. 用户个人主页:
|
||||
- 新增 `/pages/user-profile/index?userId=...`。
|
||||
- 复用 `profileGet({ userId })`、`userPosts({ authorId })`,只展示公开资料与公开动态。
|
||||
- 联系人列表头像/名称点击进主页,聊天按钮保留为独立操作。
|
||||
|
||||
3. 宠物数量上限:
|
||||
- 需要先确定产品上限,例如 6 或 10。
|
||||
- 前端 `PetShelf` 达上限隐藏/禁用“添加宠物”。
|
||||
- 后端 `petSave` 新增创建前 count 校验,防止绕过前端。
|
||||
|
||||
4. 消息推送:
|
||||
- 作为订阅消息能力单独立项。
|
||||
- 需要用户授权订阅模板、发送时机、频率限制、退订处理。
|
||||
|
||||
5. 低风险清理:
|
||||
- `messageList` 回调参数 `openid` 改名为 `memberOpenid`。
|
||||
- 个人页重复 `ensureLogin` 可在重构时简化,但当前无需优先。
|
||||
- `type='nickname'` 平台限制可在开发/非真机环境补充提示,不作为功能修复。
|
||||
|
||||
## 5. 推荐实施顺序
|
||||
|
||||
| 阶段 | 内容 | 原因 |
|
||||
|------|------|------|
|
||||
| P1 | D1 互动状态一致性、D2 我的动态分页、D3 聊天图片重试、D4 帖子详情错误状态 | 直接影响用户操作结果与内容可见性 |
|
||||
| P2 | D2 评论分页、D4 广场/附近加载体验、资料编辑离开保护、D5 统计失效与会话门禁 | 提升长列表、返回刷新和基础流程可靠性 |
|
||||
| P3 | D5 缓存淘汰、D6 更多菜单/用户主页/宠物上限/推送/清理项 | 风险较低或属于产品能力扩展 |
|
||||
|
||||
## 6. 开发校验建议
|
||||
|
||||
1. 每个云函数分页改动都保留旧入参兼容,避免旧包调用失败。
|
||||
2. 点赞、收藏、关注、偏好开关需要分别模拟云函数失败,确认 UI 回滚。
|
||||
3. 图片消息需要模拟 `getTempFileURL` 失败,确认 fileId 被保留且可重试。
|
||||
4. 使用 `npm run typecheck` 验证类型改动。
|
||||
5. 微信开发者工具中重点回归:广场搜索、附近首次进入、聊天轮询、我的动态滚动加载、帖子详情评论加载。
|
||||
Reference in New Issue
Block a user