From a8454cbaac55c4665248af5a43dc664ad1dc7b37 Mon Sep 17 00:00:00 2001 From: geekgeekrun Date: Sat, 24 Jan 2026 22:14:00 +0800 Subject: [PATCH] add city filter condition in script --- .../cityGroup.mjs} | 2 +- .../combineCalculator.mjs | 34 +-- .../default-config-file/boss.json | 1 + .../geek-auto-start-chat-with-boss/index.mjs | 221 +++++++++++------- .../components/CityChooser.vue | 2 +- 5 files changed, 167 insertions(+), 93 deletions(-) rename packages/{ui/src/common/constant/cityGroup.json => geek-auto-start-chat-with-boss/cityGroup.mjs} (99%) diff --git a/packages/ui/src/common/constant/cityGroup.json b/packages/geek-auto-start-chat-with-boss/cityGroup.mjs similarity index 99% rename from packages/ui/src/common/constant/cityGroup.json rename to packages/geek-auto-start-chat-with-boss/cityGroup.mjs index d1019fc..ede4428 100644 --- a/packages/ui/src/common/constant/cityGroup.json +++ b/packages/geek-auto-start-chat-with-boss/cityGroup.mjs @@ -1,4 +1,4 @@ -{ +export default { "code": 0, "message": "Success", "zpData": { diff --git a/packages/geek-auto-start-chat-with-boss/combineCalculator.mjs b/packages/geek-auto-start-chat-with-boss/combineCalculator.mjs index a5bb8e2..bbd7c3f 100644 --- a/packages/geek-auto-start-chat-with-boss/combineCalculator.mjs +++ b/packages/geek-auto-start-chat-with-boss/combineCalculator.mjs @@ -56,10 +56,11 @@ function combineWithZero(arr, min, max) { } export function* combineFiltersWithConstraintsGenerator(selectedFilters) { - const { salaryList, experienceList, degreeList, scaleList, industryList } = + const { cityList, salaryList, experienceList, degreeList, scaleList, industryList } = selectedFilters; // 生成符合限制条件的组合 + const cityComb = combineWithZero(cityList, 0, 1) // Salary: 0-1 个 const salaryComb = combineWithZero(salaryList, 0, 1) // Salary: 0-1 个 const experienceComb = combineWithZero(experienceList, 0, experienceList.length) // Experience: 0 个或更多 const degreeComb = combineWithZero(degreeList, 0, degreeList.length) // Degree: 0 个或更多 @@ -67,17 +68,20 @@ export function* combineFiltersWithConstraintsGenerator(selectedFilters) { const industryComb = combineWithZero(industryList, 0, 3) // Industry: 0-3 个 // 通过迭代生成所有组合 - for (const salary of salaryComb) { - for (const experience of experienceComb) { - for (const degree of degreeComb) { - for (const scale of scaleComb) { - for (const industry of industryComb) { - yield { - salaryList: salary, - experienceList: experience, - degreeList: degree, - scaleList: scale, - industryList: industry + for (const city of cityComb) { + for (const salary of salaryComb) { + for (const experience of experienceComb) { + for (const degree of degreeComb) { + for (const scale of scaleComb) { + for (const industry of industryComb) { + yield { + cityList: city, + salaryList: salary, + experienceList: experience, + degreeList: degree, + scaleList: scale, + industryList: industry + } } } } @@ -92,6 +96,7 @@ export function* combineFiltersWithConstraintsGenerator(selectedFilters) { // 计算符合限制条件的组合数量 export function calculateTotalCombinations(selectedFilters, includeEmptyCondition) { const { + cityList = [], salaryList = [], experienceList = [], degreeList = [], @@ -100,13 +105,14 @@ export function calculateTotalCombinations(selectedFilters, includeEmptyConditio } = selectedFilters // 生成符合限制条件的组合 + const cityComb = combineWithZero(cityList, 0, 1) // City: 0-1 个 const salaryComb = combineWithZero(salaryList, 0, 1) // Salary: 0-1 个 const experienceComb = combineWithZero(experienceList, 0, experienceList.length) // Experience: 0 个或更多 const degreeComb = combineWithZero(degreeList, 0, degreeList.length) // Degree: 0 个或更多 const scaleComb = combineWithZero(scaleList, 0, scaleList.length) // Scale: 0 个或更多 const industryComb = combineWithZero(industryList, 0, 3) // Industry: 0-3 个 - let result = [salaryComb, experienceComb, degreeComb, scaleComb, industryComb].reduce((accu, cur) => { + let result = [cityComb, salaryComb, experienceComb, degreeComb, scaleComb, industryComb].reduce((accu, cur) => { return accu * cur.length }, 1) if (!includeEmptyCondition) { @@ -146,6 +152,7 @@ export function formatStaticCombineFilters(rawStaticCombineRecommendJobFilterCon const conditions = Array.from(map.values()) const result = conditions.map((condition) => { return { + cityList: condition.city ? [condition.city] : [], salaryList: condition.salary ? [condition.salary] : [], experienceList: condition.experience ? [condition.experience] : [], degreeList: condition.degree ? [condition.degree] : [], @@ -155,6 +162,7 @@ export function formatStaticCombineFilters(rawStaticCombineRecommendJobFilterCon }) if (!result.length) { result.push({ + cityList: [], salaryList: [], experienceList: [], degreeList: [], diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json index 6c9955d..437e47f 100644 --- a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json +++ b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json @@ -1,6 +1,7 @@ { "combineRecommendJobFilterType": 1, "anyCombineRecommendJobFilter": { + "cityList": [], "salaryList": [], "experienceList": [], "degreeList": [], diff --git a/packages/geek-auto-start-chat-with-boss/index.mjs b/packages/geek-auto-start-chat-with-boss/index.mjs index dc38e43..2402466 100644 --- a/packages/geek-auto-start-chat-with-boss/index.mjs +++ b/packages/geek-auto-start-chat-with-boss/index.mjs @@ -38,6 +38,18 @@ import { } from './constant.mjs' import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser" import { waitForSageTimeOrJustContinue } from './sage-time.mjs' +import cityGroupData from './cityGroup.mjs' +const flattedCityList = [] +;(cityGroupData?.zpData?.cityGroup ?? []).forEach(it => { + const firstChar = it.firstChar + it.cityList.forEach(city => { + flattedCityList.push({ + ...city, + firstChar + }) + }) +}) + const jobFilterConditionsMapByCode = {} Object.values(jobFilterConditions).forEach(arr => { arr.forEach(option => { @@ -406,6 +418,7 @@ export function testIfJobTitleOrDescriptionSuit (jobInfo, matchLogic) { async function setFilterCondition (selectedFilters) { const { + cityList = [], salaryList = [], experienceList = [], degreeList = [], @@ -413,9 +426,9 @@ async function setFilterCondition (selectedFilters) { industryList = [] } = selectedFilters - const placeholderTexts = ['薪资待遇', '工作经验', '学历要求', '公司行业', '公司规模'] - const optionKaPrefixes = ['sel-job-rec-salary-', 'sel-job-rec-exp-', 'sel-job-rec-degree-', 'sel-industry-', 'sel-job-rec-scale-'] - const conditionArr = [salaryList, experienceList, degreeList, industryList, scaleList] + const placeholderTexts = ['城市', '薪资待遇', '工作经验', '学历要求', '公司行业', '公司规模'] + const optionKaPrefixes = ['switch_city_dialog_open', 'sel-job-rec-salary-', 'sel-job-rec-exp-', 'sel-job-rec-degree-', 'sel-industry-', 'sel-job-rec-scale-'] + const conditionArr = [cityList, salaryList, experienceList, degreeList, industryList, scaleList] console.log('current filter condition----') for (let i = 0; i < placeholderTexts.length; i++) { @@ -434,10 +447,15 @@ async function setFilterCondition (selectedFilters) { const placeholderText = placeholderTexts[i] const filterDropdownProxy = await (async () => { const jsHandle = (await page.evaluateHandle((placeholderText) => { - const filterBar = document.querySelector('.page-jobs-main .filter-condition-inner') - const dropdownEntry = filterBar.__vue__.$children.find(it => it.placeholder === placeholderText) - return dropdownEntry.$el - }, placeholderText)).asElement(); + if (placeholderText === '城市') { + return document.querySelector('.page-jobs-main .filter-condition-inner [ka="switch_city_dialog_open"]') + } + else { + const filterBar = document.querySelector('.page-jobs-main .filter-condition-inner') + const dropdownEntry = filterBar.__vue__.$children.find(it => it.placeholder === placeholderText) + return dropdownEntry?.$el + } + }, placeholderText))?.asElement(); return jsHandle })() if (!filterDropdownProxy) { @@ -446,88 +464,135 @@ async function setFilterCondition (selectedFilters) { const currentFilterConditions = conditionArr[i]; const filterDropdownCssList = await filterDropdownProxy.evaluate(el => Array.from(el.classList)); - if (!filterDropdownCssList.includes('is-select') && !currentFilterConditions.length) { - continue - } else { - const filterDropdownElBBox = await filterDropdownProxy.boundingBox() - await page.mouse.move( - filterDropdownElBBox.x + filterDropdownElBBox.width / 2, - filterDropdownElBBox.y + filterDropdownElBBox.height / 2, - ) - await sleepWithRandomDelay(500) + if (placeholderText === '城市') { + const onPageSelectedCity = filterDropdownCssList.includes('active') ? (await filterDropdownProxy.evaluate(el => el.textContent.trim())) : null + if (!onPageSelectedCity && !currentFilterConditions.length) { + continue + } else if (onPageSelectedCity === (currentFilterConditions[0] ?? null)) { + continue + } else { + if (!currentFilterConditions.length) { + const clearButtonHandle = await page.$(`.page-jobs-main .filter-condition-inner [ka="empty-filter"]`) + await clearButtonHandle.click() + } + else { + await filterDropdownProxy?.click() + await page.waitForFunction(() => { + const dialogEl = document.querySelector('.city-select-dialog') + return dialogEl && window.getComputedStyle(dialogEl).display !== 'none' + }) + const citySelectWrapperProxy = await page.waitForSelector('.city-select-wrapper') + let targetCityElJsHandle = (await page.evaluateHandle((cityName) => { + const targetCityEl = [...document.querySelectorAll('.city-select-dialog .city-select-wrapper ul.city-list-hot li')].find(it => it.textContent.trim() === cityName) ?? null + return targetCityEl + }, currentFilterConditions[0]))?.asElement() + if (!targetCityElJsHandle) { + const targetCityItem = flattedCityList.find(it => it.name === currentFilterConditions[0]) + if (!targetCityItem) { + // unexpected condition + continue + } + const firstChar = targetCityItem.firstChar + const targetCityCharListEntryHandle = await page.$(`xpath///*[contains(@class, "city-select-dialog")]//*[contains(@class, "city-select-wrapper")]//ul[contains(@class, "city-char-list")]//li[contains(text(), '${firstChar.toUpperCase()}')]`) + await targetCityCharListEntryHandle.click() + targetCityElJsHandle = (await page.evaluateHandle((cityName) => { + const targetCityEl = [...document.querySelectorAll('.city-select-dialog .city-select-wrapper .list-select-list a')].find(it => it.textContent.trim() === cityName) ?? null + return targetCityEl + }, currentFilterConditions[0]))?.asElement() + } + if (!targetCityElJsHandle) { + // unexpected condition + continue + } + await targetCityElJsHandle.click() + await sleep(1000) + } + } + } + else { + if (!filterDropdownCssList.includes('is-select') && !currentFilterConditions.length) { + continue + } else { + const filterDropdownElBBox = await filterDropdownProxy.boundingBox() + await page.mouse.move( + filterDropdownElBBox.x + filterDropdownElBBox.width / 2, + filterDropdownElBBox.y + filterDropdownElBBox.height / 2, + ) + await sleepWithRandomDelay(500) - const optionKaPrefix = optionKaPrefixes[i] - if (!currentFilterConditions.length) { - if (placeholderText === '公司行业') { - const activeOptionElAtCurrentFilterProxyList = await page.$$(`.page-jobs-main .filter-condition-inner .active[ka^="${optionKaPrefix}"]`) - for (const it of activeOptionElAtCurrentFilterProxyList) { - await it.click() + const optionKaPrefix = optionKaPrefixes[i] + if (!currentFilterConditions.length) { + if (placeholderText === '公司行业') { + const activeOptionElAtCurrentFilterProxyList = await page.$$(`.page-jobs-main .filter-condition-inner .active[ka^="${optionKaPrefix}"]`) + for (const it of activeOptionElAtCurrentFilterProxyList) { + await it.click() + } + } else { + // select 不限 immediately + const buxianOptionElProxy = await page.$(`.page-jobs-main .filter-condition-inner [ka="${optionKaPrefix}${0}"]`) + await buxianOptionElProxy.click() } } else { - // select 不限 immediately - const buxianOptionElProxy = await page.$(`.page-jobs-main .filter-condition-inner [ka="${optionKaPrefix}${0}"]`) - await buxianOptionElProxy.click() - } - } else { - //#region uncheck options perviously checked but not existed in current filter. - const activeOptionElAtCurrentFilterProxyList = await page.$$(`.page-jobs-main .filter-condition-inner .active[ka^="${optionKaPrefix}"]`) - const activeOptionValues = (await Promise.all( - activeOptionElAtCurrentFilterProxyList.map(elProxy => { - return elProxy.evaluate((el) => { - return el.getAttribute('ka') + //#region uncheck options perviously checked but not existed in current filter. + const activeOptionElAtCurrentFilterProxyList = await page.$$(`.page-jobs-main .filter-condition-inner .active[ka^="${optionKaPrefix}"]`) + const activeOptionValues = (await Promise.all( + activeOptionElAtCurrentFilterProxyList.map(elProxy => { + return elProxy.evaluate((el) => { + return el.getAttribute('ka') + }) }) - }) - )).map(it => it.replace(optionKaPrefix, '')).map(Number) - if (placeholderText !== '薪资待遇') { - for(let i = 0; i < activeOptionValues.length; i++) { - let activeValue + )).map(it => it.replace(optionKaPrefix, '')).map(Number) + if (placeholderText !== '薪资待遇') { + for(let i = 0; i < activeOptionValues.length; i++) { + let activeValue + if (placeholderText === '公司行业') { + activeValue = industryFilterConditionsMapByIndex[activeOptionValues[i]]?.code + } else { + activeValue = activeOptionValues[i] + } + const activeOptionElProxy = activeOptionElAtCurrentFilterProxyList[i] + if (!currentFilterConditions.includes(activeValue)) { + await activeOptionElProxy.click() + } + } + } + //#endregion + //#region only click the one which we need check, don't change already checked. + const conditionToCheck = currentFilterConditions.filter(it => { if (placeholderText === '公司行业') { - activeValue = industryFilterConditionsMapByIndex[activeOptionValues[i]]?.code + return !activeOptionValues.map(value => industryFilterConditionsMapByIndex[value].code).includes(it); } else { - activeValue = activeOptionValues[i] + return !activeOptionValues.includes(it) } - const activeOptionElProxy = activeOptionElAtCurrentFilterProxyList[i] - if (!currentFilterConditions.includes(activeValue)) { - await activeOptionElProxy.click() + }) + for(let j = 0; j < conditionToCheck.length; j++) { + let optionValue + if (placeholderText === '公司行业') { + optionValue = industryFilterConditionCodeToIndexMap[conditionToCheck[j]] + } else { + optionValue = conditionToCheck[j] } + await sleepWithRandomDelay(500) + const optionElProxy = await page.$(`.page-jobs-main .filter-condition-inner [ka="${optionKaPrefix}${optionValue}"]`) + if (!optionElProxy) { + continue; + } + await optionElProxy.click() } + //#endregion + //#region move out dropdown entry to make dropdown hidden + const navBarLogoElProxy = await page.$(`[ka="header-home-logo"]`) + if (navBarLogoElProxy) { + const navBarLogoElBBox = await navBarLogoElProxy.boundingBox() + await page.mouse.move( + navBarLogoElBBox.x + navBarLogoElBBox.width / 2, + navBarLogoElBBox.y + navBarLogoElBBox.height / 2, + ) + } + //#endregion } - //#endregion - //#region only click the one which we need check, don't change already checked. - const conditionToCheck = currentFilterConditions.filter(it => { - if (placeholderText === '公司行业') { - return !activeOptionValues.map(value => industryFilterConditionsMapByIndex[value].code).includes(it); - } else { - return !activeOptionValues.includes(it) - } - }) - for(let j = 0; j < conditionToCheck.length; j++) { - let optionValue - if (placeholderText === '公司行业') { - optionValue = industryFilterConditionCodeToIndexMap[conditionToCheck[j]] - } else { - optionValue = conditionToCheck[j] - } - await sleepWithRandomDelay(500) - const optionElProxy = await page.$(`.page-jobs-main .filter-condition-inner [ka="${optionKaPrefix}${optionValue}"]`) - if (!optionElProxy) { - continue; - } - await optionElProxy.click() - } - //#endregion - //#region move out dropdown entry to make dropdown hidden - const navBarLogoElProxy = await page.$(`[ka="header-home-logo"]`) - if (navBarLogoElProxy) { - const navBarLogoElBBox = await navBarLogoElProxy.boundingBox() - await page.mouse.move( - navBarLogoElBBox.x + navBarLogoElBBox.width / 2, - navBarLogoElBBox.y + navBarLogoElBBox.height / 2, - ) - } - //#endregion + await sleepWithRandomDelay(500) } - await sleepWithRandomDelay(500) } } } diff --git a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/components/CityChooser.vue b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/components/CityChooser.vue index b07f057..40a416c 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/components/CityChooser.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/components/CityChooser.vue @@ -119,7 +119,7 @@