处理北森级联选择器操作bug,修改插件面板功能,添加填写表单填写动态跟踪进度区、简历外表单信息自动保存功能和手动修改面板、插件收起、插件面板拖动功能
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user