缓存表单填写阶段去掉类名限制用findLabelForInput规则

This commit is contained in:
2026-08-04 16:29:58 +08:00
parent c9fb0c5644
commit e8c306946f
5 changed files with 69 additions and 270 deletions
+1 -6
View File
@@ -926,12 +926,7 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
if (allTitles.length > 0) {
const INPUT_SEL_JSON = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
const FORM_ITEM_SELS_JSON = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
const JSON_EXCLUDE_LABELS = ["请输入", "输入", ":", "", "请选择", "选择", "*", "必填"]
for (let tIdx = 0; tIdx < allTitles.length; tIdx++) {
+17 -66
View File
@@ -910,12 +910,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
if (allTitles.length > 0) {
const INPUT_SEL_JSON = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
const FORM_ITEM_SELS_JSON = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
const JSON_EXCLUDE_LABELS = ["请输入", "输入", ":", "", "请选择", "选择", "*", "必填"]
for (let tIdx = 0; tIdx < allTitles.length; tIdx++) {
@@ -1151,13 +1146,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
// 【独立模块】可以通过注释 handleAutoFillCommon 中的调用行来禁用
// ============================================================
/** 阶段B2 表单项容器选择器 */
const B2_FORM_ITEM_SELS = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
/** 阶段B2 input 选择器 */
const B2_INPUT_SEL = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
@@ -1188,6 +1177,7 @@ interface FillCachedDataResult {
/**
* 阶段B2:在指定大标题范围内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabel(
labelText: string,
@@ -1202,27 +1192,10 @@ function b2FindInputByLabel(
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
container = inp.closest(sel)
if (container) break
}
if (container) {
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1230,6 +1203,7 @@ function b2FindInputByLabel(
/**
* 阶段B2:在指定容器内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabelInContainer(
labelText: string,
@@ -1239,27 +1213,10 @@ function b2FindInputByLabelInContainer(
const allInputs = containerEl.querySelectorAll(B2_INPUT_SEL)
for (const inp of Array.from(allInputs)) {
if (usedInputs.has(inp)) continue
let formItem: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
formItem = inp.closest(sel)
if (formItem) break
}
if (formItem) {
const labelEls = formItem.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1428,23 +1385,17 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
} else {
console.log(` [B2] "${sectionData.title}" (其他经历) 缓存${segments.length}`)
// 检测当前页面已有几段
// 检测当前页面已有几段(通过统计同名标签出现次数判断段数)
const allInputsInRange = document.body.querySelectorAll(B2_INPUT_SEL)
const labelsInRange: string[] = []
for (const inp of Array.from(allInputsInRange)) {
const afterTitle = !!(titleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) { container = inp.closest(sel); if (container) break }
if (!container) continue
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const t = Array.from(el.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent?.trim()).filter(Boolean).join("")
if (t && t.length <= 30 && !B2_EXCLUDE_LABELS.some((ex) => t === ex) && (el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
labelsInRange.push(t)
break
}
// 用 findLabelForInput 获取标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() && !B2_EXCLUDE_LABELS.some((ex) => detectedLabel.trim() === ex)) {
labelsInRange.push(detectedLabel.trim())
}
}
const labelCounts = new Map<string, number>()
+17 -66
View File
@@ -916,12 +916,7 @@ export async function handleAutoFillFeishu(params: AutoFillFeishuParams): Promis
if (allTitles.length > 0) {
const INPUT_SEL_JSON = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
const FORM_ITEM_SELS_JSON = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
const JSON_EXCLUDE_LABELS = ["请输入", "输入", ":", "", "请选择", "选择", "*", "必填"]
for (let tIdx = 0; tIdx < allTitles.length; tIdx++) {
@@ -1157,13 +1152,7 @@ export async function handleAutoFillFeishu(params: AutoFillFeishuParams): Promis
// 【独立模块】可以通过注释 handleAutoFillFeishu 中的调用行来禁用
// ============================================================
/** 阶段B2 表单项容器选择器 */
const B2_FORM_ITEM_SELS = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
/** 阶段B2 input 选择器 */
const B2_INPUT_SEL = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
@@ -1194,6 +1183,7 @@ interface FillCachedDataResult {
/**
* 阶段B2:在指定大标题范围内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabel(
labelText: string,
@@ -1208,27 +1198,10 @@ function b2FindInputByLabel(
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
container = inp.closest(sel)
if (container) break
}
if (container) {
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1236,6 +1209,7 @@ function b2FindInputByLabel(
/**
* 阶段B2:在指定容器内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabelInContainer(
labelText: string,
@@ -1245,27 +1219,10 @@ function b2FindInputByLabelInContainer(
const allInputs = containerEl.querySelectorAll(B2_INPUT_SEL)
for (const inp of Array.from(allInputs)) {
if (usedInputs.has(inp)) continue
let formItem: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
formItem = inp.closest(sel)
if (formItem) break
}
if (formItem) {
const labelEls = formItem.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1434,23 +1391,17 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
} else {
console.log(` [B2] "${sectionData.title}" (其他经历) 缓存${segments.length}`)
// 检测当前页面已有几段
// 检测当前页面已有几段(通过统计同名标签出现次数判断段数)
const allInputsInRange = document.body.querySelectorAll(B2_INPUT_SEL)
const labelsInRange: string[] = []
for (const inp of Array.from(allInputsInRange)) {
const afterTitle = !!(titleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) { container = inp.closest(sel); if (container) break }
if (!container) continue
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const t = Array.from(el.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent?.trim()).filter(Boolean).join("")
if (t && t.length <= 30 && !B2_EXCLUDE_LABELS.some((ex) => t === ex) && (el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
labelsInRange.push(t)
break
}
// 用 findLabelForInput 获取标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() && !B2_EXCLUDE_LABELS.some((ex) => detectedLabel.trim() === ex)) {
labelsInRange.push(detectedLabel.trim())
}
}
const labelCounts = new Map<string, number>()
+17 -66
View File
@@ -916,12 +916,7 @@ export async function handleAutoFillHotjob(params: AutoFillHotjobParams): Promis
if (allTitles.length > 0) {
const INPUT_SEL_JSON = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
const FORM_ITEM_SELS_JSON = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
const JSON_EXCLUDE_LABELS = ["请输入", "输入", ":", "", "请选择", "选择", "*", "必填"]
for (let tIdx = 0; tIdx < allTitles.length; tIdx++) {
@@ -1157,13 +1152,7 @@ export async function handleAutoFillHotjob(params: AutoFillHotjobParams): Promis
// 【独立模块】可以通过注释 handleAutoFillHotjob 中的调用行来禁用
// ============================================================
/** 阶段B2 表单项容器选择器 */
const B2_FORM_ITEM_SELS = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
/** 阶段B2 input 选择器 */
const B2_INPUT_SEL = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
@@ -1194,6 +1183,7 @@ interface FillCachedDataResult {
/**
* 阶段B2:在指定大标题范围内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabel(
labelText: string,
@@ -1208,27 +1198,10 @@ function b2FindInputByLabel(
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
container = inp.closest(sel)
if (container) break
}
if (container) {
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1236,6 +1209,7 @@ function b2FindInputByLabel(
/**
* 阶段B2:在指定容器内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabelInContainer(
labelText: string,
@@ -1245,27 +1219,10 @@ function b2FindInputByLabelInContainer(
const allInputs = containerEl.querySelectorAll(B2_INPUT_SEL)
for (const inp of Array.from(allInputs)) {
if (usedInputs.has(inp)) continue
let formItem: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
formItem = inp.closest(sel)
if (formItem) break
}
if (formItem) {
const labelEls = formItem.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1434,23 +1391,17 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
} else {
console.log(` [B2] "${sectionData.title}" (其他经历) 缓存${segments.length}`)
// 检测当前页面已有几段
// 检测当前页面已有几段(通过统计同名标签出现次数判断段数)
const allInputsInRange = document.body.querySelectorAll(B2_INPUT_SEL)
const labelsInRange: string[] = []
for (const inp of Array.from(allInputsInRange)) {
const afterTitle = !!(titleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) { container = inp.closest(sel); if (container) break }
if (!container) continue
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const t = Array.from(el.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent?.trim()).filter(Boolean).join("")
if (t && t.length <= 30 && !B2_EXCLUDE_LABELS.some((ex) => t === ex) && (el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
labelsInRange.push(t)
break
}
// 用 findLabelForInput 获取标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() && !B2_EXCLUDE_LABELS.some((ex) => detectedLabel.trim() === ex)) {
labelsInRange.push(detectedLabel.trim())
}
}
const labelCounts = new Map<string, number>()
+17 -66
View File
@@ -916,12 +916,7 @@ export async function handleAutoFillMoka(params: AutoFillMokaParams): Promise<Au
if (allTitles.length > 0) {
const INPUT_SEL_JSON = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
const FORM_ITEM_SELS_JSON = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
const JSON_EXCLUDE_LABELS = ["请输入", "输入", ":", "", "请选择", "选择", "*", "必填"]
for (let tIdx = 0; tIdx < allTitles.length; tIdx++) {
@@ -1157,13 +1152,7 @@ export async function handleAutoFillMoka(params: AutoFillMokaParams): Promise<Au
// 【独立模块】可以通过注释 handleAutoFillMoka 中的调用行来禁用
// ============================================================
/** 阶段B2 表单项容器选择器 */
const B2_FORM_ITEM_SELS = [
".form-item", ".form-group", ".form-field",
".el-form-item", ".ant-form-item", ".ant-row",
".arco-form-item", ".t-form-item", ".n-form-item",
"[class*='form-item']", "[class*='form-group']", "[class*='formItem']",
]
/** 阶段B2 input 选择器 */
const B2_INPUT_SEL = "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio']):not([type='file']):not([disabled]), textarea:not([disabled])"
@@ -1194,6 +1183,7 @@ interface FillCachedDataResult {
/**
* 阶段B2:在指定大标题范围内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabel(
labelText: string,
@@ -1208,27 +1198,10 @@ function b2FindInputByLabel(
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
container = inp.closest(sel)
if (container) break
}
if (container) {
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1236,6 +1209,7 @@ function b2FindInputByLabel(
/**
* 阶段B2:在指定容器内,通过标签文字查找对应的 input 元素
* 使用 findLabelForInput 统一标签定位(和缓存收集时一致),不依赖容器类名
*/
function b2FindInputByLabelInContainer(
labelText: string,
@@ -1245,27 +1219,10 @@ function b2FindInputByLabelInContainer(
const allInputs = containerEl.querySelectorAll(B2_INPUT_SEL)
for (const inp of Array.from(allInputs)) {
if (usedInputs.has(inp)) continue
let formItem: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) {
formItem = inp.closest(sel)
if (formItem) break
}
if (formItem) {
const labelEls = formItem.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const directText = Array.from(el.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent?.trim())
.filter(Boolean)
.join("")
if (!directText || directText.length > 30) continue
if (B2_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
// 标签文字必须严格全名匹配,不走 includes
if (directText === labelText &&
(el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
// 用 findLabelForInput 获取该 input 对应的标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() === labelText) {
return inp as HTMLInputElement | HTMLTextAreaElement
}
}
return null
@@ -1434,23 +1391,17 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
} else {
console.log(` [B2] "${sectionData.title}" (其他经历) 缓存${segments.length}`)
// 检测当前页面已有几段
// 检测当前页面已有几段(通过统计同名标签出现次数判断段数)
const allInputsInRange = document.body.querySelectorAll(B2_INPUT_SEL)
const labelsInRange: string[] = []
for (const inp of Array.from(allInputsInRange)) {
const afterTitle = !!(titleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)
const beforeNext = !nextTitleEl || !!(nextTitleEl.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_PRECEDING)
if (!afterTitle || !beforeNext) continue
let container: Element | null = null
for (const sel of B2_FORM_ITEM_SELS) { container = inp.closest(sel); if (container) break }
if (!container) continue
const labelEls = container.querySelectorAll("label, span, div, td, th, p, legend, dt")
for (const el of Array.from(labelEls)) {
const t = Array.from(el.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent?.trim()).filter(Boolean).join("")
if (t && t.length <= 30 && !B2_EXCLUDE_LABELS.some((ex) => t === ex) && (el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING)) {
labelsInRange.push(t)
break
}
// 用 findLabelForInput 获取标签文字,和缓存收集时逻辑一致
const { labelText: detectedLabel } = findLabelForInput(inp)
if (detectedLabel && detectedLabel.trim() && !B2_EXCLUDE_LABELS.some((ex) => detectedLabel.trim() === ex)) {
labelsInRange.push(detectedLabel.trim())
}
}
const labelCounts = new Map<string, number>()