- 新增 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
9.3 KiB
沟通页 Tab 导航行为与「新招呼 + 未读」初始化设计
背景
本文档记录 招聘端沟通页(/web/chat/index)左侧会话列表的 Tab 结构、
正确的导航顺序、已知的 BOSS 前端刷新特性,以及对应的自动化实现设计。
相关代码:
packages/boss-auto-browse-and-chat/chat-page-processor.mjs—startBossChatPageProcesspackages/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,不是active。switchToTab的默认 active 检测用的是active,因此对.chat-label-itemtab 不要依赖 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 直聘沟通页的未读会话列表不会自动轮询刷新。 以下两种操作能使其刷新:
- 整页 reload(
page.reload()或 F5) - 手动点击「未读」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→ 不切换(使用当前选中的全部职位)- 切换后等待 400–700 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
步骤 5–6 的「强制点击」保证无论上次运行的终止状态如何,都能进入正确的筛选视图, 且触发 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 找不到
可能原因:
- BOSS 更新了 DOM:登录后手动打开沟通页,检查是否存在
.chat-label-item[title="新招呼"] - 账号下没有「新招呼」分类:部分账号/状态下该 tab 不显示(如没有招聘职位),
此时
switchToTab会打印 warning 并继续,不影响后续流程(降级为当前 tab) - 中文 title 属性编码差异:用浏览器控制台
document.querySelector('.chat-label-item[title="新招呼"]')确认
如果处理后候选人仍然重复出现
检查步骤:
- 确认日志中「未读」tab 的点击确实发生(不是 skip 返回)
- 确认
CHAT_PAGE_UNREAD_FILTER_SELECTOR指向的是span:nth-child(2)而不是第 1 个 - 候选人可能已在数据库
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 |
startBossChatPageProcess、switchChatPageJobId、switchToTab |
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 架构总览 |