# 汪圈小程序 - 功能缺陷核验与修复设计 > 核验日期: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`,兼容 `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` 改为 `Map`。 - 默认 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. 微信开发者工具中重点回归:广场搜索、附近首次进入、聊天轮询、我的动态滚动加载、帖子详情评论加载。