缓存字段和简历字段冲突时的逻辑优化
This commit is contained in:
@@ -444,8 +444,15 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
|
|||||||
// 【独立步骤】注释下面这段即可禁用阶段B2
|
// 【独立步骤】注释下面这段即可禁用阶段B2
|
||||||
{
|
{
|
||||||
const resumeName = currentResumeData?.main?.resumeName || currentResumeData?.main?.name || ""
|
const resumeName = currentResumeData?.main?.resumeName || currentResumeData?.main?.name || ""
|
||||||
|
// 构建阶段B中简历无值未填写的非经历字段映射(标签文字→input元素),传给B2作为 fallback 定位
|
||||||
|
const unfilledMainFieldMap = new Map<string, HTMLInputElement | HTMLTextAreaElement>()
|
||||||
|
for (const f of mainFields) {
|
||||||
|
if (!f.fillValue && f.inputElement && !usedInputs.has(f.inputElement)) {
|
||||||
|
unfilledMainFieldMap.set(f.labelText, f.inputElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
const b2Result = await handleFillCachedData({
|
const b2Result = await handleFillCachedData({
|
||||||
lang, resumeName, usedInputs, expandedResults, sectionResults,
|
lang, resumeName, usedInputs, expandedResults, sectionResults, unfilledMainFieldMap,
|
||||||
})
|
})
|
||||||
result.success += b2Result.success
|
result.success += b2Result.success
|
||||||
result.failed += b2Result.failed
|
result.failed += b2Result.failed
|
||||||
@@ -1130,7 +1137,7 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
|
|||||||
let cacheData: any = null
|
let cacheData: any = null
|
||||||
|
|
||||||
if (existing && existing.resumeName === resumeName && existing.unfilledFormData) {
|
if (existing && existing.resumeName === resumeName && existing.unfilledFormData) {
|
||||||
// 同一份简历,用新数据替换同名 section(保留其他平台的 section)
|
// 同一份简历,按字段级合并(非空值才更新,保留旧缓存中已有的非空值不被覆盖)
|
||||||
const oldSections = existing.unfilledFormData as any[]
|
const oldSections = existing.unfilledFormData as any[]
|
||||||
const newSections = unfilledFormData as any[]
|
const newSections = unfilledFormData as any[]
|
||||||
|
|
||||||
@@ -1139,9 +1146,45 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
|
|||||||
if (oldSecIdx < 0) {
|
if (oldSecIdx < 0) {
|
||||||
oldSections.push(newSec)
|
oldSections.push(newSec)
|
||||||
} else {
|
} else {
|
||||||
|
const oldSec = oldSections[oldSecIdx]
|
||||||
|
if (newSec.isExperience && oldSec.isExperience) {
|
||||||
|
// 经历类型:按段合并,保留旧字段
|
||||||
|
const oldSegments = oldSec.formItems as any[][]
|
||||||
|
const newSegments = newSec.formItems as any[][]
|
||||||
|
for (let sIdx = 0; sIdx < newSegments.length; sIdx++) {
|
||||||
|
if (sIdx >= oldSegments.length) {
|
||||||
|
oldSegments.push(newSegments[sIdx])
|
||||||
|
} else {
|
||||||
|
const oldFields = oldSegments[sIdx]
|
||||||
|
const newFields = newSegments[sIdx]
|
||||||
|
for (const nf of newFields) {
|
||||||
|
const of_ = oldFields.find((f: any) => f.label === nf.label)
|
||||||
|
if (of_) {
|
||||||
|
if (nf.value) of_.value = nf.value
|
||||||
|
} else {
|
||||||
|
oldFields.push(nf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!newSec.isExperience && !oldSec.isExperience) {
|
||||||
|
// 非经历类型:按字段合并,只有非空值才更新,旧值不丢失
|
||||||
|
const oldFields = oldSec.formItems as any[]
|
||||||
|
const newFields = newSec.formItems as any[]
|
||||||
|
for (const nf of newFields) {
|
||||||
|
const of_ = oldFields.find((f: any) => f.label === nf.label)
|
||||||
|
if (of_) {
|
||||||
|
if (nf.value) of_.value = nf.value
|
||||||
|
} else {
|
||||||
|
oldFields.push(nf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 类型变化(经历↔非经历),直接替换
|
||||||
oldSections[oldSecIdx] = newSec
|
oldSections[oldSecIdx] = newSec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
existing.unfilledFormData = oldSections
|
existing.unfilledFormData = oldSections
|
||||||
existing.timestamp = Date.now()
|
existing.timestamp = Date.now()
|
||||||
@@ -1197,6 +1240,8 @@ interface FillCachedDataParams {
|
|||||||
usedInputs: Set<Element>
|
usedInputs: Set<Element>
|
||||||
expandedResults: ExperienceSectionLocateResult[]
|
expandedResults: ExperienceSectionLocateResult[]
|
||||||
sectionResults: ExperienceSectionLocateResult[]
|
sectionResults: ExperienceSectionLocateResult[]
|
||||||
|
/** 阶段B中简历无值未填写的非经历字段(标签→input映射),B2用缓存值填写这些字段 */
|
||||||
|
unfilledMainFieldMap?: Map<string, HTMLInputElement | HTMLTextAreaElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 阶段B2 结果 */
|
/** 阶段B2 结果 */
|
||||||
@@ -1466,7 +1511,7 @@ async function b2FillBeisenRadioGroup(
|
|||||||
* 阶段B2 主流程:从缓存读取 unfilledFormData,填写有值的字段
|
* 阶段B2 主流程:从缓存读取 unfilledFormData,填写有值的字段
|
||||||
*/
|
*/
|
||||||
async function handleFillCachedData(params: FillCachedDataParams): Promise<FillCachedDataResult> {
|
async function handleFillCachedData(params: FillCachedDataParams): Promise<FillCachedDataResult> {
|
||||||
const { lang, resumeName, usedInputs, expandedResults, sectionResults } = params
|
const { lang, resumeName, usedInputs, expandedResults, sectionResults, unfilledMainFieldMap } = params
|
||||||
const result: FillCachedDataResult = { success: 0, failed: 0, skipped: 0 }
|
const result: FillCachedDataResult = { success: 0, failed: 0, skipped: 0 }
|
||||||
|
|
||||||
const cacheData = await storageGet<any>("offerpie_unfilled_form")
|
const cacheData = await storageGet<any>("offerpie_unfilled_form")
|
||||||
@@ -1618,7 +1663,15 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
|
|||||||
console.log(` [B2] "${sectionData.title}" (非经历) 缓存${fields.length}个字段`)
|
console.log(` [B2] "${sectionData.title}" (非经历) 缓存${fields.length}个字段`)
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (!field.value) { result.skipped++; continue }
|
if (!field.value) { result.skipped++; continue }
|
||||||
const inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
|
let inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
|
||||||
|
// fallback:如果 b2FindInputByLabel 找不到,从阶段B传入的未填简历字段映射中查找
|
||||||
|
if (!inputEl && unfilledMainFieldMap) {
|
||||||
|
const fallbackEl = unfilledMainFieldMap.get(field.label)
|
||||||
|
if (fallbackEl && !usedInputs.has(fallbackEl)) {
|
||||||
|
inputEl = fallbackEl
|
||||||
|
console.log(` [B2] "${field.label}" 通过 unfilledMainFieldMap fallback 定位到 input`)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (inputEl) {
|
if (inputEl) {
|
||||||
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
|
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
|
||||||
if (b2IsDateTimeLabel(field.label)) {
|
if (b2IsDateTimeLabel(field.label)) {
|
||||||
@@ -1742,7 +1795,7 @@ export async function fillBeisenCascadePicker(
|
|||||||
forceSetValue(searchInput, fillValue)
|
forceSetValue(searchInput, fillValue)
|
||||||
searchInput.dispatchEvent(new Event("input", { bubbles: true }))
|
searchInput.dispatchEvent(new Event("input", { bubbles: true }))
|
||||||
searchInput.dispatchEvent(new Event("change", { bubbles: true }))
|
searchInput.dispatchEvent(new Event("change", { bubbles: true }))
|
||||||
await delay("mid") // 等待搜索结果刷新(太短会导致选项列表未更新,选到错误选项)
|
await delay("high") // 等待搜索结果刷新(地区类级联数据量大,需要更长等待时间)
|
||||||
|
|
||||||
// 6. 检测选项集合位置(两种情况)
|
// 6. 检测选项集合位置(两种情况)
|
||||||
// 在 left-container 下查找
|
// 在 left-container 下查找
|
||||||
|
|||||||
@@ -428,8 +428,15 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
|
|||||||
// 【独立步骤】注释下面这段即可禁用阶段B2
|
// 【独立步骤】注释下面这段即可禁用阶段B2
|
||||||
{
|
{
|
||||||
const resumeName = currentResumeData?.main?.resumeName || currentResumeData?.main?.name || ""
|
const resumeName = currentResumeData?.main?.resumeName || currentResumeData?.main?.name || ""
|
||||||
|
// 构建阶段B中简历无值未填写的非经历字段映射(标签文字→input元素),传给B2作为 fallback 定位
|
||||||
|
const unfilledMainFieldMap = new Map<string, HTMLInputElement | HTMLTextAreaElement>()
|
||||||
|
for (const f of mainFields) {
|
||||||
|
if (!f.fillValue && f.inputElement && !usedInputs.has(f.inputElement)) {
|
||||||
|
unfilledMainFieldMap.set(f.labelText, f.inputElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
const b2Result = await handleFillCachedData({
|
const b2Result = await handleFillCachedData({
|
||||||
lang, resumeName, usedInputs, expandedResults, sectionResults,
|
lang, resumeName, usedInputs, expandedResults, sectionResults, unfilledMainFieldMap,
|
||||||
})
|
})
|
||||||
result.success += b2Result.success
|
result.success += b2Result.success
|
||||||
result.failed += b2Result.failed
|
result.failed += b2Result.failed
|
||||||
@@ -1065,7 +1072,7 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
|
|||||||
let cacheData: any = null
|
let cacheData: any = null
|
||||||
|
|
||||||
if (existing && existing.resumeName === resumeName && existing.unfilledFormData) {
|
if (existing && existing.resumeName === resumeName && existing.unfilledFormData) {
|
||||||
// 同一份简历,用新数据替换同名 section(保留其他平台的 section)
|
// 同一份简历,按字段级合并(非空值才更新,保留旧缓存中已有的非空值不被覆盖)
|
||||||
const oldSections = existing.unfilledFormData as any[]
|
const oldSections = existing.unfilledFormData as any[]
|
||||||
const newSections = unfilledFormData as any[]
|
const newSections = unfilledFormData as any[]
|
||||||
|
|
||||||
@@ -1074,9 +1081,45 @@ export async function handleAutoFillCommon(params: AutoFillCommonParams): Promis
|
|||||||
if (oldSecIdx < 0) {
|
if (oldSecIdx < 0) {
|
||||||
oldSections.push(newSec)
|
oldSections.push(newSec)
|
||||||
} else {
|
} else {
|
||||||
|
const oldSec = oldSections[oldSecIdx]
|
||||||
|
if (newSec.isExperience && oldSec.isExperience) {
|
||||||
|
// 经历类型:按段合并,保留旧字段
|
||||||
|
const oldSegments = oldSec.formItems as any[][]
|
||||||
|
const newSegments = newSec.formItems as any[][]
|
||||||
|
for (let sIdx = 0; sIdx < newSegments.length; sIdx++) {
|
||||||
|
if (sIdx >= oldSegments.length) {
|
||||||
|
oldSegments.push(newSegments[sIdx])
|
||||||
|
} else {
|
||||||
|
const oldFields = oldSegments[sIdx]
|
||||||
|
const newFields = newSegments[sIdx]
|
||||||
|
for (const nf of newFields) {
|
||||||
|
const of_ = oldFields.find((f: any) => f.label === nf.label)
|
||||||
|
if (of_) {
|
||||||
|
if (nf.value) of_.value = nf.value
|
||||||
|
} else {
|
||||||
|
oldFields.push(nf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!newSec.isExperience && !oldSec.isExperience) {
|
||||||
|
// 非经历类型:按字段合并,只有非空值才更新,旧值不丢失
|
||||||
|
const oldFields = oldSec.formItems as any[]
|
||||||
|
const newFields = newSec.formItems as any[]
|
||||||
|
for (const nf of newFields) {
|
||||||
|
const of_ = oldFields.find((f: any) => f.label === nf.label)
|
||||||
|
if (of_) {
|
||||||
|
if (nf.value) of_.value = nf.value
|
||||||
|
} else {
|
||||||
|
oldFields.push(nf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 类型变化(经历↔非经历),直接替换
|
||||||
oldSections[oldSecIdx] = newSec
|
oldSections[oldSecIdx] = newSec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
existing.unfilledFormData = oldSections
|
existing.unfilledFormData = oldSections
|
||||||
existing.timestamp = Date.now()
|
existing.timestamp = Date.now()
|
||||||
@@ -1132,6 +1175,8 @@ interface FillCachedDataParams {
|
|||||||
usedInputs: Set<Element>
|
usedInputs: Set<Element>
|
||||||
expandedResults: ExperienceSectionLocateResult[]
|
expandedResults: ExperienceSectionLocateResult[]
|
||||||
sectionResults: ExperienceSectionLocateResult[]
|
sectionResults: ExperienceSectionLocateResult[]
|
||||||
|
/** 阶段B中简历无值未填写的非经历字段(标签→input映射),B2用缓存值填写这些字段 */
|
||||||
|
unfilledMainFieldMap?: Map<string, HTMLInputElement | HTMLTextAreaElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 阶段B2 结果 */
|
/** 阶段B2 结果 */
|
||||||
@@ -1310,7 +1355,7 @@ async function b2FillDateTimeField(
|
|||||||
* 阶段B2 主流程:从缓存读取 unfilledFormData,填写有值的字段
|
* 阶段B2 主流程:从缓存读取 unfilledFormData,填写有值的字段
|
||||||
*/
|
*/
|
||||||
async function handleFillCachedData(params: FillCachedDataParams): Promise<FillCachedDataResult> {
|
async function handleFillCachedData(params: FillCachedDataParams): Promise<FillCachedDataResult> {
|
||||||
const { lang, resumeName, usedInputs, expandedResults, sectionResults } = params
|
const { lang, resumeName, usedInputs, expandedResults, sectionResults, unfilledMainFieldMap } = params
|
||||||
const result: FillCachedDataResult = { success: 0, failed: 0, skipped: 0 }
|
const result: FillCachedDataResult = { success: 0, failed: 0, skipped: 0 }
|
||||||
|
|
||||||
const cacheData = await storageGet<any>("offerpie_unfilled_form")
|
const cacheData = await storageGet<any>("offerpie_unfilled_form")
|
||||||
@@ -1454,7 +1499,15 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
|
|||||||
console.log(` [B2] "${sectionData.title}" (非经历) 缓存${fields.length}个字段`)
|
console.log(` [B2] "${sectionData.title}" (非经历) 缓存${fields.length}个字段`)
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (!field.value) { result.skipped++; continue }
|
if (!field.value) { result.skipped++; continue }
|
||||||
const inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
|
let inputEl = b2FindInputByLabel(field.label, titleEl, nextTitleEl, usedInputs)
|
||||||
|
// fallback:如果 b2FindInputByLabel 找不到,从阶段B传入的未填简历字段映射中查找
|
||||||
|
if (!inputEl && unfilledMainFieldMap) {
|
||||||
|
const fallbackEl = unfilledMainFieldMap.get(field.label)
|
||||||
|
if (fallbackEl && !usedInputs.has(fallbackEl)) {
|
||||||
|
inputEl = fallbackEl
|
||||||
|
console.log(` [B2] "${field.label}" 通过 unfilledMainFieldMap fallback 定位到 input`)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!inputEl) { result.skipped++; continue }
|
if (!inputEl) { result.skipped++; continue }
|
||||||
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
|
// 时间/日期字段走阶段A同款流程:直接点击 input → fillDatePicker,不经过 detectPickerField
|
||||||
if (b2IsDateTimeLabel(field.label)) {
|
if (b2IsDateTimeLabel(field.label)) {
|
||||||
|
|||||||
+3
-3
@@ -8,9 +8,9 @@ export type DelayLevel = "low" | "mid" | "high" | "max"
|
|||||||
|
|
||||||
/** 各等级对应的毫秒数(统一在此处调整) */
|
/** 各等级对应的毫秒数(统一在此处调整) */
|
||||||
const DELAY_MS: Record<DelayLevel, number> = {
|
const DELAY_MS: Record<DelayLevel, number> = {
|
||||||
low: 10, // <100ms 场景:微等待,点击后极短暂停
|
low: 10, // 原<100ms 场景:微等待,点击后极短暂停
|
||||||
mid: 50, // 100~300ms 场景:等待 DOM 更新、弹出层渲染
|
mid: 30, // 原100~300ms 场景:等待 DOM 更新、弹出层渲染
|
||||||
high: 500, // >300ms 场景:等待接口返回、搜索结果、动画完成
|
high: 500, // 原>300ms 场景:等待接口返回、搜索结果、动画完成
|
||||||
max: 2000, // >2000ms 场景:特殊超长延时(谨慎使用)
|
max: 2000, // >2000ms 场景:特殊超长延时(谨慎使用)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user