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

223 lines
9.3 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.
# 沟通页 Tab 导航行为与「新招呼 + 未读」初始化设计
## 背景
本文档记录 **招聘端沟通页**`/web/chat/index`)左侧会话列表的 Tab 结构、
正确的导航顺序、已知的 BOSS 前端刷新特性,以及对应的自动化实现设计。
相关代码:
- `packages/boss-auto-browse-and-chat/chat-page-processor.mjs``startBossChatPageProcess`
- `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`,不是 `active`。`switchToTab` 的默认 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. **整页 reload**`page.reload()` 或 F5
2. **手动点击「未读」tab**
如果程序已在「未读」tab 停留,且直接解析 DOM解析到的是**上次点击时的快照**
而非当前真实未读状态。已处理(点击后已读)的会话不会从列表消失。
### 旧代码的 bug
`switchToTab` 的实现含有如下提前返回逻辑:
```js
if (isActive) {
logDebug(`已在「${tabName}」tab`)
return // ← 跳过了点击BOSS 不会刷新列表
}
```
如果上次运行结束时页面停留在「未读」tab下次运行到达这段代码时
`isActive === true`,点击被跳过 → 列表未刷新 → 已处理的候选人被重复遍历。
### 修复方案
在每次 `startBossChatPageProcess` 进入处理循环前,用 `force: true` 强制点击
「新招呼」和「未读」,绕过 active 检测:
```js
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` 签名改为:
```js
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` | `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 架构总览 |