通用模式兼容摩卡和飞书,添加通信工具组件,添加未开通会员面板

This commit is contained in:
2026-07-17 20:04:29 +08:00
parent bca1ca3182
commit bfe13d300b
17 changed files with 1997 additions and 456 deletions
+26 -101
View File
@@ -19,6 +19,8 @@ import { locateExperienceSections, expandExperienceSections, sortExperienceByTim
import type { ExperienceSectionLocateResult } from "~lib/experienceSection"
import type { MatchedFormField, ResumeData, ExperienceSection, JobInfo, UnmatchedFormField } from "~lib/types"
import { setFieldHighlight, isRequiredField } from "~lib/formStyle"
import { get as storageGet, set as storageSet } from "~utils/storage"
import { findLabelForInput } from "~lib/labelFinder"
/** 北森模式自动填写的参数 */
export interface AutoFillBeisenParams {
@@ -957,41 +959,10 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
if (bgColor === "#b7ffc5" || bgColor === "rgb(183, 255, 197)") continue
}
// 查找标签
let labelText = ""
let container: Element | null = null
for (const sel of FORM_ITEM_SELS_JSON) {
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 (JSON_EXCLUDE_LABELS.some((ex) => directText === ex)) continue
if (el.compareDocumentPosition(inp) & Node.DOCUMENT_POSITION_FOLLOWING) {
labelText = directText
break
}
}
}
if (!labelText) {
let prev: Element | null = inp.previousElementSibling
for (let i = 0; i < 3 && prev; i++) {
const text = prev.textContent?.trim() || ""
if (text && text.length <= 20 && !JSON_EXCLUDE_LABELS.some((ex) => text === ex)) {
labelText = text
break
}
prev = prev.previousElementSibling
}
}
if (!labelText) labelText = inputEl.getAttribute("placeholder") || "(未知字段)"
// 查找标签(使用统一封装的 labelFinder
const titleElementSet = new Set<Element>(allTitles.map((t) => t.element))
const { labelText: detectedLabel } = findLabelForInput(inp, titleElementSet)
const labelText = detectedLabel
// 非经历类型:跳过简历格式字段
// 【注意】简历格式字段通过 resumeFormatInputs(阶段A/B实际匹配到的input元素集合)精确跳过
@@ -1114,72 +1085,29 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
console.log(JSON.stringify(unfilledFormData, null, 2))
console.log("===== OfferPie: JSON 输出完毕 =====")
// 存 localStorage 缓存,包含简历名字和未填字段数据
// 更新规则:以第一次存的版本为基准,可以添加字段、更新 value,但不删除已有字段
// 存 chrome.storage 缓存,包含简历名字和未填字段数据(跨域名共享,按简历名区分)
const resumeName = currentResumeData?.main?.resumeName || currentResumeData?.main?.name || ""
try {
const existingRaw = localStorage.getItem("offerpie_unfilled_form")
const existing = await storageGet<any>("offerpie_unfilled_form")
let cacheData: any = null
if (existingRaw) {
const existing = JSON.parse(existingRaw)
if (existing.resumeName === resumeName && existing.unfilledFormData) {
// 同一份简历,合并更新(不删除已有字段,只添加新字段和更新 value)
const oldSections = existing.unfilledFormData as any[]
const newSections = unfilledFormData as any[]
if (existing && existing.resumeName === resumeName && existing.unfilledFormData) {
// 同一份简历,用新数据替换同名 section(保留其他平台的 section
const oldSections = existing.unfilledFormData as any[]
const newSections = unfilledFormData as any[]
// 遍历新数据,对每个 section 做合并
for (const newSec of newSections) {
const oldSec = oldSections.find((s: any) => s.title === newSec.title)
if (!oldSec) {
// 新增的 section,直接追加
oldSections.push(newSec)
continue
}
if (newSec.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])
continue
}
// 已有的段:合并字段
const oldFields = oldSegments[sIdx]
const newFields = newSegments[sIdx]
for (const newField of newFields) {
const oldField = oldFields.find((f: any) => f.label === newField.label)
if (oldField) {
// 已有字段:如果新值非空则更新
if (newField.value) oldField.value = newField.value
} else {
// 新字段:追加
oldFields.push(newField)
}
}
}
} else {
// 非经历类型:按字段合并
const oldFields = oldSec.formItems as any[]
const newFields = newSec.formItems as any[]
for (const newField of newFields) {
const oldField = oldFields.find((f: any) => f.label === newField.label)
if (oldField) {
if (newField.value) oldField.value = newField.value
} else {
oldFields.push(newField)
}
}
}
for (const newSec of newSections) {
const oldSecIdx = oldSections.findIndex((s: any) => s.title === newSec.title)
if (oldSecIdx < 0) {
oldSections.push(newSec)
} else {
oldSections[oldSecIdx] = newSec
}
existing.unfilledFormData = oldSections
existing.timestamp = Date.now()
cacheData = existing
}
existing.unfilledFormData = oldSections
existing.timestamp = Date.now()
cacheData = existing
}
// 没有已有缓存或不是同一份简历 → 新建
@@ -1191,10 +1119,10 @@ export async function handleAutoFillBeisen(params: AutoFillBeisenParams): Promis
}
}
localStorage.setItem("offerpie_unfilled_form", JSON.stringify(cacheData))
await storageSet("offerpie_unfilled_form", cacheData)
console.log(`===== OfferPie: 已缓存未填字段数据(简历: "${resumeName}" =====`)
} catch (e) {
console.warn("OfferPie: localStorage 缓存失败", e)
console.warn("OfferPie: chrome.storage 缓存失败", e)
}
}
@@ -1503,15 +1431,12 @@ async function handleFillCachedData(params: FillCachedDataParams): Promise<FillC
const { lang, resumeName, usedInputs, expandedResults, sectionResults } = params
const result: FillCachedDataResult = { success: 0, failed: 0, skipped: 0 }
const cachedRaw = localStorage.getItem("offerpie_unfilled_form")
if (!cachedRaw) {
const cacheData = await storageGet<any>("offerpie_unfilled_form")
if (!cacheData) {
console.log("===== OfferPie: 阶段B2 - 无缓存数据,跳过 =====")
return result
}
let cacheData: any
try { cacheData = JSON.parse(cachedRaw) } catch { return result }
if (cacheData.resumeName !== resumeName) {
console.log(`===== OfferPie: 阶段B2 - 缓存简历名"${cacheData.resumeName}"与当前"${resumeName}"不匹配,跳过 =====`)
return result