处理北森级联选择器操作bug,修改插件面板功能,添加填写表单填写动态跟踪进度区、简历外表单信息自动保存功能和手动修改面板、插件收起、插件面板拖动功能

This commit is contained in:
2026-06-18 16:42:02 +08:00
parent 6be840a416
commit bca1ca3182
9 changed files with 2499 additions and 310 deletions
+101 -17
View File
@@ -13,6 +13,7 @@ import { matchFormFieldsInRange, matchMainFields } from "~lib/formMatcher"
import { detectPickerField } from "~lib/pickerDetector"
import { detectAndUploadResume } from "~lib/resumeUpload"
import { getMockResumeData2, JOB_FORM_LABELS } from "~lib/constants"
import { fillDatePicker } from "~lib/datePicker"
import { getResumeFieldValue } from "~lib/resumeDataHelper"
import { locateExperienceSections, expandExperienceSections, sortExperienceByTime, relocateSegmentContainer, getAllPageTitles, findAddButton, clickAddButton } from "~lib/experienceSection"
import type { ExperienceSectionLocateResult } from "~lib/experienceSection"
@@ -45,6 +46,10 @@ export interface AutoFillCommonResult {
formFields: MatchedFormField[]
/** 收集到的待填写空白字段 */
unmatchedFields: UnmatchedFormField[]
/** 经历段落定位结果(用于 fillStats 分段展示) */
sectionResults: ExperienceSectionLocateResult[]
/** 经历段落展开结果(用于 fillStats 分段展示) */
expandedResults: ExperienceSectionLocateResult[]
}
/**
@@ -58,6 +63,8 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
resumeData: params.resumeData,
formFields: [],
unmatchedFields: [],
sectionResults: [],
expandedResults: [],
}
// 1. 提取 DOM 结构
@@ -101,6 +108,10 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
// 4.6 对比简历数据段数,点击添加按钮补足不够的段数
const expandedResults = await expandExperienceSections(sectionResults, currentResumeData, lang)
// 存入 result 供外部使用(如 fillStats 分段展示)
result.sectionResults = sectionResults
result.expandedResults = expandedResults
// 4.65 【预匹配+标绿】经历段落添加完成后,一次性预匹配所有有简历数据的字段并标绿背景
// 使用临时 usedInputs,不影响后续正式流程的匹配
{
@@ -318,7 +329,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
const ok = await fillSearchPickerField(f)
if (ok) { result.success++ } else { result.failed++ }
phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: ok, fillValue: f.fillValue, section: f.section, segmentIndex: segIdx })
await delay(200)
await delay(100)
continue
} else {
await detectPickerField(f, lang)
@@ -326,14 +337,14 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
if (ok) { result.success++ } else { result.failed++ }
}
// 收集阶段A已处理字段(用于末尾统计
// 收集阶段A已处理字段(用于末尾統計
phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: !!f.fillValue, fillValue: f.fillValue, section: f.section, segmentIndex: segIdx })
// 关闭残留弹窗
if (lastTextInput && !lastTextInputIsPicker) {
;(lastTextInput as HTMLElement).click()
lastTextInput.focus()
await delay(100)
await delay(50)
lastTextInput.blur()
} else {
if (document.activeElement instanceof HTMLElement) {
@@ -344,7 +355,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
}
document.body.click()
}
await delay(300)
await delay(150)
if (!f.isPicker && f.inputElement && !isTimePeriodField(f.key) && !isTimeSingleField(f.key)) {
lastTextInput = f.inputElement
@@ -391,7 +402,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
if (lastTextInput) {
;(lastTextInput as HTMLElement).click()
lastTextInput.focus()
await delay(100)
await delay(50)
lastTextInput.blur()
} else {
if (document.activeElement instanceof HTMLElement) {
@@ -402,7 +413,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
}
document.body.click()
}
await delay(300)
await delay(150)
if (!f.isPicker && f.inputElement) lastTextInput = f.inputElement
}
@@ -1035,7 +1046,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
}
console.log("===== OfferPie: 网站未填表单字段数据(JSON =====")
console.log(JSON.stringify(unfilledFormData, null, 2))
// console.log(JSON.stringify(unfilledFormData, null, 2))
console.log("===== OfferPie: JSON 输出完毕 =====")
// 存 localStorage 缓存,包含简历名字和未填字段数据
@@ -1274,6 +1285,61 @@ async function b2FillSingleField(
return await fillMatchedField(field)
}
/** 时间/日期相关的标签关键字 */
const B2_DATE_TIME_KEYWORDS = ["时间", "日期", "日期时间", "开始", "结束", "起始", "截止", "入职", "离职", "毕业"]
/**
* 判断标签名是否为时间/日期类字段
* 标签文字中包含时间/日期相关关键字即认为是时间字段
*/
function b2IsDateTimeLabel(label: string): boolean {
return B2_DATE_TIME_KEYWORDS.some((kw) => label.includes(kw))
}
/**
* 阶段B2:填写时间/日期字段
* 走和阶段A同款的流程:直接点击 input → fillDatePicker
* 不经过 detectPickerField(避免方式3主动点击导致 toggle 问题)
* 不经过 fillPickerField(避免步骤1-2再次点击关闭弹出层)
*
* 引用方式和阶段A的 fillTimePeriodField 情况A2 一致:
* input.focus() → input.click() → delay → fillDatePicker(field)
*/
async function b2FillDateTimeField(
inputEl: HTMLInputElement | HTMLTextAreaElement,
labelText: string,
fillValue: string
): Promise<boolean> {
// 构造 MatchedFormField(和阶段A一样,isPicker=true,不设 pickerDropdownElement
const field: MatchedFormField = {
key: "", section: "main", resumeField: "",
sectionIndex: 0, labelText,
labelElement: inputEl.previousElementSibling || inputEl.parentElement || inputEl,
labelSelector: "",
inputElement: inputEl,
inputSelector: buildSelector(inputEl),
buttonElement: null, buttonSelector: "",
inputType: "text",
radioContainer: null,
isPicker: true,
pickerDropdownElement: null, pickerDropdownSelector: "",
fillValue,
}
// 和阶段A同样的方式:点击 input 展开日期面板,然后直接调 fillDatePicker
inputEl.focus()
;(inputEl as HTMLElement).click()
await delay(150)
const ok = await fillDatePicker(field)
if (ok) {
console.log(`OfferPie: ✅ [B2-时间字段] "${labelText}" = "${fillValue}" 填写成功`)
} else {
console.log(`OfferPie: ❌ [B2-时间字段] "${labelText}" = "${fillValue}" 填写失败`)
}
return ok
}
/**
* 阶段B2 主流程:从缓存读取 unfilledFormData,填写有值的字段
*/
@@ -1340,9 +1406,15 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
if (!field.value) { result.skipped++; continue }
const inputEl = b2FindInputByLabelInContainer(field.label, seg.containerElement, usedInputs)
if (!inputEl) { result.skipped++; continue }
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
await delay(200)
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
if (b2IsDateTimeLabel(field.label)) {
const ok = await b2FillDateTimeField(inputEl, field.label, field.value)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
} else {
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
}
await delay(100)
}
}
} else {
@@ -1392,7 +1464,7 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
}).length
const clicked = await clickAddButton(addBtn, titleEl, nextTitleEl, beforeCount)
if (!clicked) { console.log(` [B2] "${sectionData.title}" 点击添加按钮无效`); break }
await delay(300)
await delay(150)
}
}
@@ -1402,9 +1474,15 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
if (!field.value) { result.skipped++; continue }
const inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
if (!inputEl) { result.skipped++; continue }
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
await delay(200)
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
if (b2IsDateTimeLabel(field.label)) {
const ok = await b2FillDateTimeField(inputEl, field.label, field.value)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
} else {
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
}
await delay(100)
}
}
}
@@ -1415,9 +1493,15 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
if (!field.value) { result.skipped++; continue }
const inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
if (!inputEl) { result.skipped++; continue }
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
await delay(200)
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
if (b2IsDateTimeLabel(field.label)) {
const ok = await b2FillDateTimeField(inputEl, field.label, field.value)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
} else {
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
}
await delay(100)
}
}
}