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