输入框修改颜色微调,弹出层下拉选项数据收集修改关闭弹出层的办法,飞书经历添加按钮添加专属兼容
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { EXPERIENCE_SECTION_CONFIGS, JOB_FORM_LABELS } from "./constants"
|
import { EXPERIENCE_SECTION_CONFIGS, JOB_FORM_LABELS } from "./constants"
|
||||||
import { delay } from "~utils/delay"
|
import { delay } from "~utils/delay"
|
||||||
|
import { simulateClick } from "~utils/domEvent"
|
||||||
import { snapshotElementsInRange, diffSnapshots, findTopLevelNewElements } from "./dom"
|
import { snapshotElementsInRange, diffSnapshots, findTopLevelNewElements } from "./dom"
|
||||||
import { findNearestInput } from "./formMatcher"
|
import { findNearestInput } from "./formMatcher"
|
||||||
import type { ExperienceSection, ExperienceSectionConfig, ResumeData } from "./types"
|
import type { ExperienceSection, ExperienceSectionConfig, ResumeData } from "./types"
|
||||||
@@ -732,12 +733,50 @@ function getDirectText(el: Element): string {
|
|||||||
*
|
*
|
||||||
* 【注意】验证 fullText 不能包含其他经历类型的标题关键词,防止跨区域误匹配
|
* 【注意】验证 fullText 不能包含其他经历类型的标题关键词,防止跨区域误匹配
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【飞书专用】在大标题到下一大标题范围内查找添加按钮
|
||||||
|
* 飞书招聘平台的添加按钮使用 ud__button ud__button--text 类名的 button 标签
|
||||||
|
* 通用 findAddButton 在飞书上第二次定位时会误匹配到包含字段文字的大容器,因此飞书走此独立逻辑
|
||||||
|
*
|
||||||
|
* @param titleEl - 当前经历区块的大标题元素
|
||||||
|
* @param nextTitleEl - 下一个大标题元素(范围边界)
|
||||||
|
* @returns 找到的添加按钮 button 元素,未找到返回 null
|
||||||
|
*/
|
||||||
|
function findFeishuAddButton(titleEl: Element, nextTitleEl: Element | null): Element | null {
|
||||||
|
// 在范围内查找所有 button.ud__button.ud__button--text
|
||||||
|
const allButtons = document.querySelectorAll("button.ud__button.ud__button--text")
|
||||||
|
for (const btn of Array.from(allButtons)) {
|
||||||
|
// 判断是否在 titleEl 之后
|
||||||
|
const afterTitle = !!(titleEl.compareDocumentPosition(btn) & Node.DOCUMENT_POSITION_FOLLOWING)
|
||||||
|
if (!afterTitle) continue
|
||||||
|
// 判断是否在 nextTitleEl 之前(如果有下一大标题)
|
||||||
|
if (nextTitleEl) {
|
||||||
|
const beforeNext = !!(nextTitleEl.compareDocumentPosition(btn) & Node.DOCUMENT_POSITION_PRECEDING)
|
||||||
|
if (!beforeNext) continue
|
||||||
|
}
|
||||||
|
// 确认按钮文字含"添加"关键词(排除删除等其他操作按钮)
|
||||||
|
const btnText = btn.textContent?.trim() || ""
|
||||||
|
if (btnText.includes("添加") || btnText.includes("Add")) {
|
||||||
|
return btn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export function findAddButton(
|
export function findAddButton(
|
||||||
titleEl: Element,
|
titleEl: Element,
|
||||||
nextTitleEl: Element | null,
|
nextTitleEl: Element | null,
|
||||||
config: ExperienceSectionConfig,
|
config: ExperienceSectionConfig,
|
||||||
lang: "zh" | "en"
|
lang: "zh" | "en"
|
||||||
): Element | null {
|
): Element | null {
|
||||||
|
// 【飞书特有逻辑】飞书招聘平台的添加按钮是 button.ud__button.ud__button--text
|
||||||
|
// 通用逻辑在飞书上第二次查找会误匹配到整个段容器,因此飞书走独立的定位策略
|
||||||
|
if (window.location.hostname.includes("feishu.cn")) {
|
||||||
|
const feishuBtn = findFeishuAddButton(titleEl, nextTitleEl)
|
||||||
|
if (feishuBtn) return feishuBtn
|
||||||
|
}
|
||||||
|
|
||||||
const addKeywords = lang === "zh" ? config.addButtonZh : config.addButtonEn
|
const addKeywords = lang === "zh" ? config.addButtonZh : config.addButtonEn
|
||||||
// 当前经历类型的标题关键词(用于验证按钮确实属于本区块)
|
// 当前经历类型的标题关键词(用于验证按钮确实属于本区块)
|
||||||
const sectionKeywords = lang === "zh" ? config.zh : config.en
|
const sectionKeywords = lang === "zh" ? config.zh : config.en
|
||||||
@@ -842,7 +881,7 @@ export async function clickAddButton(
|
|||||||
|
|
||||||
// 逐层点击,每层点击后检测 input 数量是否增加
|
// 逐层点击,每层点击后检测 input 数量是否增加
|
||||||
for (const target of clickTargets) {
|
for (const target of clickTargets) {
|
||||||
target.click()
|
simulateClick(target)
|
||||||
await delay("high")
|
await delay("high")
|
||||||
|
|
||||||
const afterInputs = findInputsInRange(titleEl, nextTitleEl)
|
const afterInputs = findInputsInRange(titleEl, nextTitleEl)
|
||||||
|
|||||||
+25
-2
@@ -15,6 +15,7 @@ import type { MatchedFormField, UnmatchedFormField } from "./types"
|
|||||||
import { isRequiredField, setFieldHighlight } from "./formStyle"
|
import { isRequiredField, setFieldHighlight } from "./formStyle"
|
||||||
import { findLabelForInput } from "./labelFinder"
|
import { findLabelForInput } from "./labelFinder"
|
||||||
import { delay } from "~utils/delay"
|
import { delay } from "~utils/delay"
|
||||||
|
import { simulateClick } from "~utils/domEvent"
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// 一、类型定义
|
// 一、类型定义
|
||||||
@@ -1236,12 +1237,34 @@ export async function collectPickerOptionTexts(fieldStats: TitleStat[]): Promise
|
|||||||
await delay("midH")
|
await delay("midH")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 循环结束后,点击 document.documentElement 关闭最后一个可能残留的弹出层
|
// 循环结束后,点击第一个已识别字段的输入框,通过 simulateClick 完整事件链关闭所有残留弹出层
|
||||||
document.documentElement.dispatchEvent(new MouseEvent("click", { bubbles: true, clientX: 0, clientY: 0 }))
|
const firstInputEl = findFirstVisibleInput(fieldStats)
|
||||||
|
if (firstInputEl) {
|
||||||
|
simulateClick(firstInputEl)
|
||||||
|
await delay("mid")
|
||||||
|
}
|
||||||
|
|
||||||
return optionTexts
|
return optionTexts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从字段统计列表中找到第一个可见的 input 元素
|
||||||
|
* 用于点击关闭残留弹出层(simulateClick 触发完整事件链,兼容所有 UI 组件库)
|
||||||
|
*
|
||||||
|
* @param fieldStats - scanPageFields 返回的按大标题分组的字段统计数组
|
||||||
|
* @returns 第一个可见的 input HTMLElement,找不到返回 null
|
||||||
|
*/
|
||||||
|
function findFirstVisibleInput(fieldStats: TitleStat[]): HTMLElement | null {
|
||||||
|
for (const ts of fieldStats) {
|
||||||
|
for (const f of ts.fields) {
|
||||||
|
if (!f.inputElement) continue
|
||||||
|
const el = f.inputElement as HTMLElement
|
||||||
|
if (el.offsetHeight > 0 && el.offsetWidth > 0) return el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归收集指定元素内所有可见叶子节点的文字
|
* 递归收集指定元素内所有可见叶子节点的文字
|
||||||
* 只收集有效文字(长度 2-30,至少含1个汉字或2个英文字母),排除纯数字/纯符号
|
* 只收集有效文字(长度 2-30,至少含1个汉字或2个英文字母),排除纯数字/纯符号
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const COLOR_MAP: Record<HighlightColor, string> = {
|
|||||||
green: "#b7ffc5", // 淡绿色
|
green: "#b7ffc5", // 淡绿色
|
||||||
greenTwo: "#b7ffc6", // 淡绿色2
|
greenTwo: "#b7ffc6", // 淡绿色2
|
||||||
red: "#ffb7b7", // 淡红色
|
red: "#ffb7b7", // 淡红色
|
||||||
yellow: "#fff", // 白色
|
yellow: "transparent", // 透明色
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 常见的表单项容器选择器(与 formMatcher.ts 保持一致) */
|
/** 常见的表单项容器选择器(与 formMatcher.ts 保持一致) */
|
||||||
|
|||||||
Reference in New Issue
Block a user