Files
geekgeekrun/plan/chat_page_tab_navigation.md
rqi14 0bb94409a2 feat(boss): 登录后自动关闭治理公告弹窗;沟通页新招呼/未读 tab 初始化
- 新增 GOVERNANCE_NOTICE_DIALOG_* 选择器与 dismissGovernanceNoticeDialog(),
  在 launchBrowserAndNavigateToChat 与 startBossAutoBrowse 登录后调用,避免阻塞自动化。
- 沟通页:先切「新招呼」再强制点「未读」刷新列表;switchToTab 支持 force 选项。
- 文档:recruiter_architecture §14 弹窗清单与 §6 常量更新;CLAUDE.md 补充登录后弹窗说明;
  新增 plan/README.md、plan/chat_page_tab_navigation.md。

Made-with: Cursor
2026-03-27 12:04:13 +08:00

9.3 KiB
Raw Blame History

沟通页 Tab 导航行为与「新招呼 + 未读」初始化设计

背景

本文档记录 招聘端沟通页/web/chat/index)左侧会话列表的 Tab 结构、 正确的导航顺序、已知的 BOSS 前端刷新特性,以及对应的自动化实现设计。

相关代码:

  • packages/boss-auto-browse-and-chat/chat-page-processor.mjsstartBossChatPageProcess
  • packages/boss-auto-browse-and-chat/constant.mjs — 所有选择器

1. 沟通页左侧面板的 UI 层次

沟通页左侧面板存在两套独立的过滤控件,功能和 DOM 结构完全不同,容易混淆:

1-A. 会话类型 Tab.chat-label-item

位于左侧列表的顶部,按消息来源类型分类:

Tab 名称 DOM 选择器 选中态 class 含义
全部 .chat-label-item[title="全部"] selected 不限类型
新招呼 .chat-label-item[title="新招呼"] selected 候选人主动发来的第一条招呼
沟通中 .chat-label-item[title="沟通中"] selected 已有来回消息的会话
已获取简历 .chat-label-item[title="已获取简历"] selected 简历已获取
已交换微信 .chat-label-item[title="已交换微信"] selected 微信已交换

注意:选中态 class 是 selected,不是 activeswitchToTab 的默认 active 检测用的是 active,因此对 .chat-label-item tab 不要依赖 active 检测, 应使用 force: true 强制点击。

1-B. 已读/未读状态 Tab.chat-message-filter-left span

位于 1-A 之下,按已读/未读状态过滤当前类型内的会话:

Tab 名称 DOM 选择器 选中态 class 含义
全部 .chat-message-filter-left span:nth-child(1) active 不限已读/未读
未读 .chat-message-filter-left span:nth-child(2) active 只显示未读会话

这里的"全部"与 1-A 的"全部"是不同的控件选择器、class 名、语义均不同, 不要混淆。


2. 正确的手动操作顺序(同事记录的复现路径)

1. 点击「全部职位」(展开顶部职位下拉框)
2. 点击目标职位(如「实验室技术员」)→ 会话列表切换为该职位
3. 点击「新招呼」1-A tab→ 只显示候选人主动打招呼的会话
4. 点击「未读」1-B tab→ BOSS 刷新未读列表
5. 开始逐条处理

步骤 3新招呼和步骤 4未读缺一不可

  • 省略步骤 3 → 在「全部类型」下,工具会看到「沟通中」「已获取简历」等其他类型的候选人, 处理范围远超预期(遍历全部职位的全部类型消息)。
  • 省略步骤 4 或不强制点击 → BOSS 不刷新列表,上次已处理(已读)的候选人会继续出现, 导致重复操作(详见第 3 节)。

3. BOSS 「未读」列表不自动刷新的特性

BOSS 直聘沟通页的未读会话列表不会自动轮询刷新。 以下两种操作能使其刷新:

  1. 整页 reloadpage.reload() 或 F5
  2. 手动点击「未读」tab

如果程序已在「未读」tab 停留,且直接解析 DOM解析到的是上次点击时的快照 而非当前真实未读状态。已处理(点击后已读)的会话不会从列表消失。

旧代码的 bug

switchToTab 的实现含有如下提前返回逻辑:

if (isActive) {
  logDebug(`已在「${tabName}」tab`)
  return  // ← 跳过了点击BOSS 不会刷新列表
}

如果上次运行结束时页面停留在「未读」tab下次运行到达这段代码时 isActive === true,点击被跳过 → 列表未刷新 → 已处理的候选人被重复遍历。

修复方案

在每次 startBossChatPageProcess 进入处理循环前,用 force: true 强制点击 「新招呼」和「未读」,绕过 active 检测:

await switchToTab(CHAT_PAGE_TAB_NEW_GREET_SELECTOR, '新招呼', { force: true })
await sleepWithRandomDelay(300, 500)
await switchToTab(CHAT_PAGE_UNREAD_FILTER_SELECTOR, '未读', { force: true })
await sleepWithRandomDelay(400, 600)

switchToTab 签名改为:

const switchToTab = async (selector, tabName, opts = {}) => {
  if (!opts.force) {
    // 检测 active class已激活则跳过用于非刷新场景
  }
  // ... 拟人点击 ...
}

4. 职位下拉框(.chat-top-job)的切换逻辑

沟通页顶部有职位筛选下拉框,切换后左侧列表只显示该职位的会话。

DOM 元素 常量名 说明
触发按钮 CHAT_PAGE_JOB_DROPDOWN_SELECTOR .chat-top-job .ui-dropmenu-label
展开后列表项 CHAT_PAGE_JOB_ITEM_SELECTOR .chat-top-job .ui-dropmenu-list li

切换函数:switchChatPageJobId(page, jobId)(同文件内部函数)。

  • jobId === '-1'jobId == null → 不切换(使用当前选中的全部职位)
  • 切换后等待 400700 ms 让列表刷新
  • 若下拉列表中未找到目标 jobId会打印 warning 并跳过(不抛异常)

调试提示:如果切换职位后列表仍显示所有职位的消息, 先确认 boss-jobs-config.json 中的 jobId 字段值与 BOSS 页面 下拉框 li[value] 的值一致(通过 sync-boss-job-list IPC 同步可以保证这一点)。


5. 完整的 Tab 初始化序列(当前实现)

每次调用 startBossChatPageProcess 时,按以下顺序执行:

1. 确认当前在沟通页 URL否则 goto
2. setupNetworkInterceptor
3. waitForSelector(CHAT_PAGE_ITEM_SELECTOR, timeout=15s)
4. switchChatPageJobId若 jobId 有效)
5. 【新招呼 force】switchToTab(CHAT_PAGE_TAB_NEW_GREET_SELECTOR, { force: true })
6. 【未读 force】switchToTab(CHAT_PAGE_UNREAD_FILTER_SELECTOR, { force: true })
7. [可选] retryCandidate
     switchToTab(ALL_FILTER, '全部')       ← 找已读候选人
     processOneCandidateConversation(...)
     switchToTab(UNREAD_FILTER, '未读')    ← 切回(不 force正常切换即可
8. parseConversationList → process loop

步骤 56 的「强制点击」保证无论上次运行的终止状态如何,都能进入正确的筛选视图, 且触发 BOSS 的未读列表数据刷新。


6. 验证 Tab 初始化是否生效的方法

日志关键字

成功路径(logLevel: 'info' 或更详细)应出现:

[chat-page-processor] 切换到「新招呼」tab...
[chat-page-processor] 「新招呼」tab 切换后列表已刷新
[chat-page-processor] 切换到「未读」tab...
[chat-page-processor] 「未读」tab 切换后列表已刷新

失败路径(元素未找到):

[chat-page-processor] 未找到「新招呼」tab 元素selector: .chat-label-item[title="新招呼"]

如果「新招呼」tab 找不到

可能原因:

  1. BOSS 更新了 DOM:登录后手动打开沟通页,检查是否存在 .chat-label-item[title="新招呼"]
  2. 账号下没有「新招呼」分类:部分账号/状态下该 tab 不显示(如没有招聘职位), 此时 switchToTab 会打印 warning 并继续,不影响后续流程(降级为当前 tab
  3. 中文 title 属性编码差异:用浏览器控制台 document.querySelector('.chat-label-item[title="新招呼"]') 确认

如果处理后候选人仍然重复出现

检查步骤:

  1. 确认日志中「未读」tab 的点击确实发生(不是 skip 返回)
  2. 确认 CHAT_PAGE_UNREAD_FILTER_SELECTOR 指向的是 span:nth-child(2) 而不是第 1 个
  3. 候选人可能已在数据库 encryptGeekId 记录中但未标记为 contacted 此时会被 checkIfAlreadyContacted 放过 → 检查数据库记录

7. retryCandidate 流程与 Tab 状态

retryCandidate 是验证中断恢复流程(被 BOSS 安全验证打断时),此阶段 Tab 状态:

步骤 1-A 类型 tab 1-B 状态 tab 说明
进入 retry 前 新招呼force 切入) 未读force 切入) 初始化阶段已设置
retry 内切换 新招呼(保持) 全部 候选人已读,需看全部
retry 结束 新招呼(保持) 未读 切回,准备正常扫描
正常扫描 新招呼 未读 初始化状态,直接 parseConversationList

retry 结束时的 switchToTab(UNREAD, '未读') 不需要 force: true 因为这只是从「全部」切回「未读」的正常操作BOSS 会正常刷新列表。


8. 相关文件

文件 作用
packages/boss-auto-browse-and-chat/chat-page-processor.mjs startBossChatPageProcessswitchChatPageJobIdswitchToTab
packages/boss-auto-browse-and-chat/constant.mjs 所有 tab/选择器常量(CHAT_PAGE_TAB_NEW_GREET_SELECTOR 等)
packages/ui/src/main/flow/BOSS_CHAT_PAGE_MAIN/index.ts Worker 入口,读取 boss-jobs-config.json 并按职位循环调用 startBossChatPageProcess
plan/multi-job-switching.md 多职位配置文件结构、sync-boss-job-list IPC 实现
plan/boss_auto_browse_tabs.md 推荐牛人页与沟通页双 Tab 架构总览