diff --git a/src/handlers/handleAutoFillBeisen.ts b/src/handlers/handleAutoFillBeisen.ts index f3a6b5f..75b463b 100644 --- a/src/handlers/handleAutoFillBeisen.ts +++ b/src/handlers/handleAutoFillBeisen.ts @@ -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++) { diff --git a/src/handlers/handleAutoFillCommon.ts b/src/handlers/handleAutoFillCommon.ts index 8992bdc..3bb3ae7 100644 --- a/src/handlers/handleAutoFillCommon.ts +++ b/src/handlers/handleAutoFillCommon.ts @@ -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 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() diff --git a/src/handlers/handleAutoFillFeishu.ts b/src/handlers/handleAutoFillFeishu.ts index 050d50d..ba4df3d 100644 --- a/src/handlers/handleAutoFillFeishu.ts +++ b/src/handlers/handleAutoFillFeishu.ts @@ -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 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() diff --git a/src/handlers/handleAutoFillHotjob.ts b/src/handlers/handleAutoFillHotjob.ts index e452edf..6d7786b 100644 --- a/src/handlers/handleAutoFillHotjob.ts +++ b/src/handlers/handleAutoFillHotjob.ts @@ -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 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() diff --git a/src/handlers/handleAutoFillMoka.ts b/src/handlers/handleAutoFillMoka.ts index 140c1d9..e294838 100644 --- a/src/handlers/handleAutoFillMoka.ts +++ b/src/handlers/handleAutoFillMoka.ts @@ -916,12 +916,7 @@ export async function handleAutoFillMoka(params: AutoFillMokaParams): Promise 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 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 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()