Files
offerpai_browser_plug/.kiro/steering/project-guidelines.md
T

157 lines
13 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.
---
inclusion: always
---
# 项目开发规范与注意事项
## 技术栈
- Plasmo + React + TypeScript
- 样式:SCSS
## 项目结构
```
src/
├── api/
│ ├── aiApi.ts # AI后端接口(Python后端,简历优化/AI生成等)
│ ├── dataApi.ts # 数据后端接口(Java后端,简历CRUD/岗位/会员等)
│ └── request.ts # 请求封装(axios实例、拦截器、Token注入)
├── background/
│ └── index.ts # 插件后台脚本(Service Worker
├── components/
│ ├── SidebarPanel.tsx # 侧边栏面板组件(主操作界面,自动填写入口,字段进度展示)
│ └── SidebarPanel.scss # 侧边栏样式
├── contents/
│ └── sidebar.tsx # Content Script 入口(注入侧边栏到页面)
├── handlers/
│ ├── handleAutoFillCommon.ts # 通用模式自动填写(不依赖任何CSS类名,纯DOM差异对比适配所有网站)
│ └── handleAutoFillBeisen.ts # 北森模式自动填写(针对北森Phoenix UI的特殊处理,含级联选择器等)
├── lib/
│ ├── types.ts # 所有共享类型定义(简历接口、表单标签、匹配字段、UI组件库配置、经历区块类型)
│ ├── constants.ts # 常量数据(JOB_FORM_LABELS标签数组、EXPERIENCE_SECTION_CONFIGS经历配置、UI_LIB_PICKER_CONFIGS组件库配置)
│ ├── dom.ts # DOM工具(extractDomStructure提取DOM树、detectPageLanguage语言检测、isJobApplicationForm表单页判断、buildSelector选择器生成、snapshotElementsInRange/diffSnapshots/findTopLevelNewElements DOM快照差异对比)
│ ├── formMatcher.ts # 表单字段匹配(matchFormFields全页面匹配、matchFormFieldsInRange范围内匹配、matchMainFields非经历区域匹配、findNearestInput查找input
│ ├── labelFinder.ts # 标签定位(findLabelForInput通过form-item容器+DOM逆向遍历两遍策略识别input对应的标签文字)
│ ├── pickerDetector.ts # 选择器识别(detectPickerField检测字段是否为选择器类型,通过UI组件库类名/提示文字/主动点击DOM差异对比三种方式)
│ ├── pickerFill.ts # 选择器选项匹配与点击(fuzzyMatchScore模糊匹配、clickBestOptionInDropdown弹出层内选项点击、findAndClickOptionInVisiblePopups弹出层搜索)
│ ├── datePicker.ts # 日期选择器填写(fillDatePicker日期填写、detectNavButtons按钮探测、navigateToYearMonth导航、tryFillMonthPanel月份面板)
│ ├── autofill.ts # 自动填写核心(forceSetValue强制写值、fillMatchedField统一填写入口、fillPickerField选择器填写含DOM差异对比、fillSearchPickerField搜索型选择器、fillTimePeriodField/fillTimeSingleField时间字段、fillYearMonthPicker年月选择器)
│ ├── experienceSection.ts # 经历区块识别与段数管理(locateExperienceSections定位5大经历、expandExperienceSections补足段数、countExistingSegments段数校正、relocateSegmentContainer重定位、sortExperienceByTime时间排序)
│ ├── fillStats.ts # 填写统计(scanPageFields按大标题分组扫描所有字段状态、extractNonResumeFields提取非简历格式字段、printFieldStats打印统计)
│ ├── formStyle.ts # 表单样式(setFieldHighlight字段背景高亮、isRequiredField必填检测)
│ ├── resumeDataHelper.ts # 简历数据工具(getResumeFieldValue根据section/index/field获取简历值)
│ ├── resumeUpload.ts # 简历上传(detectAndUploadResume检测上传按钮并从OSS下载注入文件)
│ └── channelBridge.ts # 跨域通信桥(panelBridge跨域读写数据,用于Content Script和网页端通信)
├── utils/
│ ├── auth.ts # 认证工具(getMemberStatus会员状态查询、getCustomizeResume定制简历获取)
│ ├── cookie.ts # Cookie工具(getCookieValue从浏览器获取Cookie值)
│ ├── delay.ts # 延时工具(delay统一延时函数,按等级管理:low/mid/high/max
│ └── storage.ts # 存储工具(chrome.storage的get/set/remove封装)
└── assets/
├── icon.png # 插件图标
└── logo-offerpai.png # OfferPai logo
```
## 编码规范
- 页面结构和ts的常量变量和方法都要加中文注释
- 重要逻辑加【注意】标记防止误删(如fillPickerField的DOM差异对比逻辑)
## 注意事项
- 项目接口有Java后端和Python AI后端两种,如果是联调加入新接口,一定要确认有没有说是哪个后端的
- fillPickerField中的DOM差异对比弹出层检测是核心机制,不要简化或删除
- detectPickerField的方式3(主动点击检测DOM变化)是自建组件选择器识别的关键,不要删除
- UI_LIB_PICKER_CONFIGS里不要加容易误匹配的配置(如之前Beisen Phoenix被误匹配到新东方自建网站)
## 填充操作核心规范
- **所有字段填充必须走 `fillMatchedField` 统一入口**,禁止在外部自行编写填充逻辑
- **选择器类型字段(下拉、日期、月份、级联等)绝对不能用 `forceSetValue` 直接写值**,这类 input 通常是受控组件,直接写值无效
- **选择器类型检测必须走 `detectPickerField` 统一入口**pickerDetector.ts),禁止在外部自行判断字段是否为选择器
- detectPickerField 内部已封装三种检测方式:UI组件库类名匹配 → 提示文字检测 → 主动点击DOM差异对比
- 不要自行通过 readonly、class 等属性简单判断是否为选择器,这样会漏判
- 时间字段(开始时间/结束时间/起止时间)如果没有找到 `placeholder="年"/"月"` 的下拉输入框,必须标记 `isPicker=true``fillMatchedField``fillPickerField``fillDatePicker``tryFillMonthPanel` 已封装的完整链路
- **禁止在新增代码中自行编写选择器展开、月份点击、日期导航等操作逻辑**,必须先查看项目中已封装的方法并直接引用:
- 选择器类型检测 → `pickerDetector.ts`detectPickerField
- 日期/月份选择器操作 → `datePicker.ts`fillDatePicker / tryFillMonthPanel / navigateToYearMonth / clickDayCell
- 下拉选项匹配与点击 → `pickerFill.ts`clickBestOptionInDropdown / findAndClickOptionInVisiblePopups
- 选择器展开与DOM差异检测 → `autofill.ts` 中的 `fillPickerField`
- 单选按钮点击 → `autofill.ts` 中的 `fillRadioField`
- 搜索型选择器 → `autofill.ts` 中的 `fillSearchPickerField`
- 年月下拉选择器 → `autofill.ts` 中的 `fillYearMonthPicker`
- `datePicker.ts` 中的按钮探测逻辑(detectNavButtons:逐个点击观察年月变化)是通用日期选择器适配的核心,不要简化或删除
## 经历段落定位核心规范
- `experienceSection.ts` 中的段落容器定位逻辑(DOM 差异对比 + 特征重建)是5大经历区域填写的核心,**不可擅自修改或简化**
- 核心原理:
1. 点击添加按钮前后,用 `snapshotElementsInRange` / `diffSnapshots` / `findTopLevelNewElements`dom.ts)对比 DOM 快照差异
2. 找到新增的顶层容器(一段经历的完整 DOM),记住它的 tag + className 前缀作为段容器特征
3. 同时保存新增段容器的 DOM 引用(`lastNewContainerEl`
4. 所有段添加完后,从 `lastNewContainerEl.parentElement` 出发,在父级 children 中匹配所有同特征兄弟容器
5. 按 DOM 顺序重建 `segmentRanges` 数组,确保每段经历的 containerElement 精确对应
- **禁止**用 input 差集(`beforeInputs`)来定位段容器——React 重渲染会导致原始段的 input 节点被重建,差集不可靠
- **禁止**依赖 `titleElement` 存活状态来做重建——React 重渲染可能使 titleElement 脱离 DOM
- 对于初始有1段且不需要添加的经历:走 `detectFirstSegment` 的 fallback 逻辑(无同级兄弟时 containerElement 可能为 null`matchFormFieldsInRange` 有 fallback 用 startElement/endElement 范围搜索)
## 下拉选择器弹出层搜索核心规范
- **弹出层检测不依赖任何固定类名**,全部走 DOM 差异对比(点击前后全页面快照对比新增/新可见元素)
- **禁止使用 POPUP_SELECTORS 或任何固定 CSS 选择器来检测弹出层**,必须走全页面可见性差异对比(`offsetHeight > 0 && offsetWidth > 0`
- **pickerDetector.ts 方式3(主动点击检测)的核心约束**:
- 检测到弹出层后 **禁止关闭弹出层**(不调用 dismissPopup
- 将弹出层 DOM 引用保存到 `field.pickerDropdownElement`
- fillPickerField 步骤0 直接在已有弹出层内搜索,避免二次点击触发 toggle 关闭
- 弹出层由用户选择后自动关闭,无需手动干预
- **选项搜索必须限定在检测到的弹出层元素内**,禁止全页面搜:
- `findAndClickOptionInVisiblePopups(fillValue, labelText, popupEl?)` 没有 `popupEl` 时直接返回 false,不做全页面搜
- `fillPickerField` 步骤0 用 `field.pickerDropdownElement`pickerDetector 保存的引用)在弹出层内搜索
- `fillPickerField` 步骤4 在 DOM 差异检测到的 `dropdownEl` 内搜索
- `fillSearchPickerField` 轮询检测到的 `newPopupEl` 传给 `clickBestOptionInDropdown``findAndClickOptionInVisiblePopups`
- **fillPickerField 步骤0**pickerDetector 可能已展开弹出层(toggle 冲突问题),所以在步骤1之前先尝试在当前页面搜索匹配选项,避免二次点击关闭弹出层。**不可删除此步骤**
- **isAsyncDropdown 字段的特殊处理**FormLabelItem.isAsyncDropdown = true):
- 接口异步下拉字段(如学校、专业)输入值后需轮询等待接口返回数据(最多3秒)
- 弹出层检测阈值为 ≥1 个可见子元素(普通字段为 ≥2)
- 检测到弹出层后优先用 `clickBestOptionInDropdown(newPopupEl)` 在弹出层内直接做文字匹配
- 禁止对 isAsyncDropdown 字段走 `tryClickDropdownListItem` 全页面搜(选项数可能为1,全页面搜会被其他同规格元素干扰)
- `pickerFill.ts``groupBySpec` / `findVisibleListGroups` 的同规格标签组阈值为 ≥2(不可改回 ≥3,否则只有2个选项的下拉列表会漏匹配)
- `clickBestOptionInDropdown` 是在指定弹出层 DOM 内递归搜所有可见叶子文字节点做模糊匹配的核心方法,**不可简化其递归逻辑**
## 延时调用规范
- **项目中所有延时调用必须且只能使用 `src/utils/delay.ts` 中导出的 `delay` 函数**
- 禁止在任何文件中自行编写 `setTimeout` / `new Promise(resolve => setTimeout(...))` 等延时逻辑
- `delay` 函数接收延时等级参数,统一管理延时时长:
- `delay("low")` — 低延时(微等待,如点击后极短暂停)
- `delay("mid")` — 中延时(等待 DOM 更新、弹出层渲染等)
- `delay("midH")` — 中延时偏高(稍微加长版等待 DOM 更新、弹出层渲染等)
- `delay("high")` — 高延时(等待接口返回、搜索结果、动画完成等)
- `delay("max")` — 特殊超长延时(谨慎使用)
- 具体毫秒数只在 `src/utils/delay.ts` 中统一设置,调用方只使用等级名称
- 如需调整全局延时策略,只修改 `delay.ts` 中的 `DELAY_MS` 常量即可
## 代码组织与可维护性规范
- **修改或添加新逻辑时,必须完整查看逻辑相关的所有引用和引入**,理解上下游调用关系后再动手
- **只要能实现,新逻辑优先单独抽出为独立方法**,通过返回值向调用方提供所需结果,保持项目的解耦性和高可维护性
- 禁止将新增逻辑直接内联到已有的大方法中导致方法膨胀、职责混乱
- 独立方法应有清晰的中文注释说明:用途、入参、返回值、安全退化行为
- 调用独立方法的位置需加注释说明为什么调用、结果如何使用
## 弹出层点击操作规范
- **任何需要触发弹出层(下拉面板、日期选择器、级联选择器等)的点击操作,禁止使用普通的 `.click()`**
- 必须使用 `src/utils/domEvent.ts` 导出的 `simulateClick` 方法:
```typescript
import { simulateClick } from "~utils/domEvent"
simulateClick(el) // 默认含 pointer 事件 + 自动取元素中心坐标
simulateClick(el, { focus: true }) // 需要先 focus 时
simulateClick(el, { pointer: false }) // 不需要 pointer 事件时
```
- 原因:React / Vue 等框架的 UI 组件库(如 Shimo Design、Ant Design 等)经常将事件监听绑定在 `mousedown` 或 `pointerdown` 而非 `click` 上,普通 `.click()` 只触发 click 事件,无法激活这些组件的弹出层逻辑
- `simulateClick` 内部完整事件链:pointerdown → mousedown → pointerup → mouseup → click
- 禁止在业务代码中自行编写 `dispatchEvent(new MouseEvent(...))` 序列,必须统一引用 `~utils/domEvent`