处理北森级联选择器操作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
+358 -22
View File
@@ -7,13 +7,14 @@
* 不要在此文件中自行编写选择器操作逻辑,必须引用 lib 中已封装的方法。
*/
import { fillMatchedField, delay, isSearchPickerField, fillSearchPickerField, isTimePeriodField, isTimeSingleField, fillTimePeriodField, fillTimeSingleField } from "~lib/autofill"
import { fillMatchedField, delay, forceSetValue, isSearchPickerField, fillSearchPickerField, isTimePeriodField, isTimeSingleField, fillTimePeriodField, fillTimeSingleField } from "~lib/autofill"
import { extractDomStructure, detectPageLanguage, isJobApplicationForm, buildSelector } from "~lib/dom"
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 { getResumeFieldValue } from "~lib/resumeDataHelper"
import { fillDatePicker } from "~lib/datePicker"
import { locateExperienceSections, expandExperienceSections, sortExperienceByTime, relocateSegmentContainer, getAllPageTitles, findAddButton, clickAddButton } from "~lib/experienceSection"
import type { ExperienceSectionLocateResult } from "~lib/experienceSection"
import type { MatchedFormField, ResumeData, ExperienceSection, JobInfo, UnmatchedFormField } from "~lib/types"
@@ -45,6 +46,10 @@ export interface AutoFillBeisenResult {
formFields: MatchedFormField[]
/** 收集到的待填写空白字段 */
unmatchedFields: UnmatchedFormField[]
/** 经历段落定位结果(用于 fillStats 分段展示) */
sectionResults: ExperienceSectionLocateResult[]
/** 经历段落展开结果(用于 fillStats 分段展示) */
expandedResults: ExperienceSectionLocateResult[]
}
/**
@@ -58,6 +63,8 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
resumeData: params.resumeData,
formFields: [],
unmatchedFields: [],
sectionResults: [],
expandedResults: [],
}
// 1. 提取 DOM 结构
@@ -101,6 +108,10 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
// 4.6 对比简历数据段数,点击添加按钮补足不够的段数
const expandedResults = await expandExperienceSections(sectionResults, currentResumeData, lang)
// 存入 result 供外部使用(如 fillStats 分段展示)
result.sectionResults = sectionResults
result.expandedResults = expandedResults
// 4.65 【预匹配+标绿】经历段落添加完成后,一次性预匹配所有有简历数据的字段并标绿背景
// 使用临时 usedInputs,不影响后续正式流程的匹配
{
@@ -318,7 +329,14 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): 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 if (isBeisenCascadeField(f.labelText) && f.inputElement) {
// 北森多级级联选择器字段(地区、籍贯、民族等)
const ok = await fillBeisenCascadePicker(f.inputElement, f.labelText, f.fillValue)
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(150)
continue
} else {
await detectPickerField(f, lang)
@@ -333,7 +351,7 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
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 +362,7 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
}
document.body.click()
}
await delay(300)
await delay(150)
if (!f.isPicker && f.inputElement && !isTimePeriodField(f.key) && !isTimeSingleField(f.key)) {
lastTextInput = f.inputElement
@@ -377,6 +395,15 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
if (!f.fillValue) { result.skipped++; continue }
// 北森多级级联选择器字段优先处理(地区、籍贯、民族等)
if (isBeisenCascadeField(f.labelText) && f.inputElement) {
console.log(` [${f.key}] "${f.labelText}" → 北森级联选择器 | fillValue: "${f.fillValue}"`)
const ok = await fillBeisenCascadePicker(f.inputElement, f.labelText, f.fillValue)
if (ok) { result.success++ } else { result.failed++ }
await delay(150)
continue
}
await detectPickerField(f, lang)
console.log(
@@ -391,7 +418,7 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): 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 +429,7 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
}
document.body.click()
}
await delay(300)
await delay(150)
if (!f.isPicker && f.inputElement) lastTextInput = f.inputElement
}
@@ -1323,6 +1350,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 填写 phoenix-radio-group 单选组字段
* 在指定大标题范围内,通过标签文字找到对应的 radio group,点击匹配选项
@@ -1378,21 +1460,21 @@ async function b2FillBeisenRadioGroup(
const childEl = item.firstElementChild as HTMLElement | null
if (childEl) {
childEl.click()
await delay(100)
await delay(50)
// 子子级也点一下确保触发
const grandChildEl = childEl.firstElementChild as HTMLElement | null
if (grandChildEl) {
grandChildEl.click()
await delay(100)
await delay(50)
}
} else {
// fallback:直接点击 radioItem 自身
;(item as HTMLElement).click()
await delay(100)
await delay(50)
}
// 4. 验证选中结果:检查子级是否出现 phoenix-radio--checked 类名
await delay(200)
await delay(100)
const checkedEl = item.querySelector(".phoenix-radio--checked")
if (checkedEl) {
// 选中成功,radio group 容器变 greenTwo
@@ -1480,9 +1562,19 @@ 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 if (isBeisenCascadeField(field.label)) {
// 北森多级级联选择器字段(地区、籍贯、民族等)
const ok = await fillBeisenCascadePicker(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 {
@@ -1532,7 +1624,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)
}
}
@@ -1542,9 +1634,19 @@ 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 if (isBeisenCascadeField(field.label)) {
// 北森多级级联选择器字段(地区、籍贯、民族等)
const ok = await fillBeisenCascadePicker(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)
}
}
}
@@ -1555,19 +1657,253 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
if (!field.value) { result.skipped++; continue }
const inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
if (inputEl) {
// 普通 input/textarea 字段
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
// 时间/日期字段走阶段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 if (isBeisenCascadeField(field.label)) {
// 北森多级级联选择器字段(地区、籍贯、民族等)
const ok = await fillBeisenCascadePicker(inputEl, field.label, field.value)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
} else {
// 普通 input/textarea 字段
const ok = await b2FillSingleField(inputEl, field.label, field.value, lang)
if (ok) { result.success++; usedInputs.add(inputEl); setFieldHighlight(inputEl, "greenTwo") } else { result.failed++ }
}
} else {
// 找不到 input,尝试北森 phoenix-radio-group 单选组
const radioOk = await b2FillBeisenRadioGroup(field.label, field.value, titleEl, nextTitleEl)
if (radioOk) { result.success++ } else { result.skipped++ }
}
await delay(200)
await delay(100)
}
}
}
console.log(`===== OfferPie: 阶段B2完成 成功${result.success} 失败${result.failed} 跳过${result.skipped} =====`)
return result
}
}
// ============================================================
// 北森多级级联选择器(cascade picker)检测与填写
// ============================================================
/**
* 北森多级级联选择器关键字
* 字段标签中包含以下关键字的,可能是级联选择器字段
*/
const BEISEN_CASCADE_KEYWORDS = ["地区", "居住地", "籍贯", "户籍", "地点", "所在地", "民族", "地址", "现居"]
/**
* 判断字段标签是否可能是北森多级级联选择器字段
* 通过标签文字中是否含有地区/籍贯/民族等关键字来判断
*/
export function isBeisenCascadeField(labelText: string): boolean {
return BEISEN_CASCADE_KEYWORDS.some((kw) => labelText.includes(kw))
}
/**
* 北森多级级联选择器填写方法
*
* 操作流程:
* 1. 点击字段对应的 input,等待弹出 DOM 面板
* 2. 在页面中找到 common-unmodeled-layer 类名的面板元素
* 3. 在面板内找到 phoenix-input__input 类名的 input 输入框
* 4. 先点击 clear-select-data 清空已选内容
* 5. 在输入框中输入要填写的值,等待 50ms
* 6. 检测选项集合位置(两种情况):
* - 情况1: left-container 下找 area-data-container,其子级 div 就是选项
* - 情况2: left-container 下找 list-data-container,其第1子div的第1子div的第1子div是选项集合
* 7. 在选项集合的第一个 div 里找 icon-container 的 span 并点击选中
* 8. 找 area-footer-button 里的确定按钮(phoenix-button / phoenix-button__wraper / phoenix-button__content 三级都点一下)
*
* @param inputEl - 字段对应的 input 元素(表单里那个输入框,用于点击触发弹出面板)
* @param labelText - 字段标签文字(用于日志)
* @param fillValue - 要填写的值
* @returns 是否填写成功
*/
export async function fillBeisenCascadePicker(
inputEl: HTMLInputElement | HTMLTextAreaElement,
labelText: string,
fillValue: string
): Promise<boolean> {
if (!inputEl || !fillValue) return false
console.log(`OfferPie: [北森级联] "${labelText}" 开始填写 "${fillValue}"`)
// 1. 点击 input 触发弹出面板
inputEl.scrollIntoView({ block: "center", behavior: "instant" })
;(inputEl as HTMLElement).click()
inputEl.focus()
await delay(400)
// 2. 在页面中找 common-unmodeled-layer 面板(取最后一个,即最新弹出的)
const allPanels = document.querySelectorAll(".common-unmodeled-layer")
const panelEl = allPanels.length > 0 ? (allPanels[allPanels.length - 1] as HTMLElement) : null
if (!panelEl) {
console.log(`OfferPie: [北森级联] "${labelText}" 未找到 common-unmodeled-layer 面板`)
return false
}
console.log(`OfferPie: [北森级联] "${labelText}" 找到级联面板 (共${allPanels.length}个,取最后一个)`)
// 3. 在面板内找搜索输入框(优先 phoenix-input__inputfallback 到面板内任意可见 input
let searchInput = panelEl.querySelector("input.phoenix-input__input") as HTMLInputElement | null
if (!searchInput) {
// fallback:在面板内找所有非 hidden 的 input,取第一个可见的
const allPanelInputs = panelEl.querySelectorAll("input:not([type='hidden'])")
console.log(`OfferPie: [北森级联] "${labelText}" phoenix-input__input 未找到,尝试 fallback (面板内共${allPanelInputs.length}个input)`)
for (const inp of Array.from(allPanelInputs)) {
const htmlInp = inp as HTMLInputElement
if (htmlInp.offsetHeight > 0 && htmlInp.offsetWidth > 0) {
searchInput = htmlInp
break
}
}
}
if (!searchInput) {
console.log(`OfferPie: [北森级联] "${labelText}" 面板内未找到搜索输入框,面板 innerHTML 前200字: ${panelEl.innerHTML.substring(0, 200)}`)
return false
}
// 4. 先点击 clear-select-data 清空已选内容
const clearBtn = panelEl.querySelector(".clear-select-data") as HTMLElement | null
if (clearBtn) {
clearBtn.click()
await delay(200) // 等待清空操作生效
console.log(`OfferPie: [北森级联] "${labelText}" 已清空已选内容`)
}
// 5. 在搜索输入框中输入要填写的值
searchInput.focus()
forceSetValue(searchInput, fillValue)
searchInput.dispatchEvent(new Event("input", { bubbles: true }))
searchInput.dispatchEvent(new Event("change", { bubbles: true }))
await delay(300) // 等待搜索结果刷新(太短会导致选项列表未更新,选到错误选项)
// 6. 检测选项集合位置(两种情况)
// 在 left-container 下查找
const leftContainer = panelEl.querySelector(".left-container") as HTMLElement | null
if (!leftContainer) {
console.log(`OfferPie: [北森级联] "${labelText}" 面板内未找到 left-container`)
return false
}
let optionClicked = false
// 情况1: area-data-container
const areaDataContainer = leftContainer.querySelector(".area-data-container") as HTMLElement | null
if (areaDataContainer) {
console.log(`OfferPie: [北森级联] "${labelText}" 检测到情况1 (area-data-container)`)
const firstOptionDiv = areaDataContainer.querySelector(":scope > div") as HTMLElement | null
if (firstOptionDiv) {
const iconSpan = firstOptionDiv.querySelector(".icon-container") as HTMLElement | null
if (iconSpan) {
iconSpan.click()
const svgEl = iconSpan.querySelector("svg") as unknown as HTMLElement | null
if (svgEl) svgEl.dispatchEvent(new MouseEvent("click", { bubbles: true }))
optionClicked = true
await delay(200)
console.log(`OfferPie: [北森级联] "${labelText}" 已点击第一个选项的 icon-container + svg`)
}
}
}
// 情况2: list-data-container
if (!optionClicked) {
const listDataContainer = leftContainer.querySelector(".list-data-container") as HTMLElement | null
if (listDataContainer) {
console.log(`OfferPie: [北森级联] "${labelText}" 检测到情况2 (list-data-container)`)
// list-data-container > 第1子div > 第1子div > 第1子div = 选项集合标签
const level1 = listDataContainer.firstElementChild as HTMLElement | null
const level2 = level1?.firstElementChild as HTMLElement | null
const level3 = level2?.firstElementChild as HTMLElement | null
if (level3) {
// 选项集合的第一个子div
const firstOption = level3.firstElementChild as HTMLElement | null
if (firstOption) {
const iconSpan = firstOption.querySelector(".icon-container") as HTMLElement | null
if (iconSpan) {
iconSpan.click()
// 同时点击 icon-container 里面的 svg
const svgEl = iconSpan.querySelector("svg") as unknown as HTMLElement | null
if (svgEl) svgEl.dispatchEvent(new MouseEvent("click", { bubbles: true }))
optionClicked = true
await delay(200)
console.log(`OfferPie: [北森级联] "${labelText}" 已点击第一个选项的 icon-container + svg`)
}
}
}
}
}
if (!optionClicked) {
console.log(`OfferPie: [北森级联] "${labelText}" 未找到可点击的选项`)
return false
}
// 8. 找确定按钮并点击(兼容两种 footer 类名:area-footer-button / selector-footer-button
// 【注意】footer 下有"取消"和"确定"两个按钮,querySelector 会取到第一个(取消),
// 所以必须通过文字"确定"精确定位 或 取最后一个 phoenix-button 才能点到确定按钮
const footerBtn = (panelEl.querySelector(".area-footer-button") || panelEl.querySelector(".selector-footer-button")) as HTMLElement | null
if (footerBtn) {
// 策略1:通过 phoenix-button__content 文字"确定"精确定位
const allContents = footerBtn.querySelectorAll(".phoenix-button__content")
let confirmContent: HTMLElement | null = null
for (const el of Array.from(allContents)) {
if (el.textContent?.trim() === "确定") {
confirmContent = el as HTMLElement
break
}
}
if (confirmContent) {
confirmContent.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }))
confirmContent.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }))
confirmContent.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }))
// 同时点击它的父级 wraper 和祖父级 button
const wraper = confirmContent.parentElement as HTMLElement | null
if (wraper) {
wraper.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }))
wraper.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }))
wraper.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }))
}
const btn = wraper?.parentElement as HTMLElement | null
if (btn) {
btn.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }))
btn.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }))
btn.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }))
}
}
// 策略2:取最后一个 phoenix-button 也点击一下
const allBtns = footerBtn.querySelectorAll(".phoenix-button")
if (allBtns.length > 0) {
const lastBtn = allBtns[allBtns.length - 1] as HTMLElement
lastBtn.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }))
lastBtn.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }))
lastBtn.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }))
// 最后一个 button 里的 wraper 和 content 也点
const lastWraper = lastBtn.querySelector(".phoenix-button__wraper") as HTMLElement | null
if (lastWraper) {
lastWraper.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }))
lastWraper.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }))
lastWraper.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }))
}
const lastContent = lastBtn.querySelector(".phoenix-button__content") as HTMLElement | null
if (lastContent) {
lastContent.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }))
lastContent.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }))
lastContent.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }))
}
}
await delay(300)
console.log(`OfferPie: [北森级联] "${labelText}" 已点击确定按钮`)
} else {
console.log(`OfferPie: [北森级联] "${labelText}" 未找到确定按钮`)
return false
}
console.log(`OfferPie: ✅ [北森级联] "${labelText}" = "${fillValue}" 填写成功`)
return true
}