mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-08 17:09:07 +08:00
Merge branch 'feature/ui'
This commit is contained in:
@@ -60,9 +60,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- os: macos-13 # for x64 build
|
- os: macos-15-intel # for x64 build
|
||||||
arch: x64
|
arch: x64
|
||||||
- os: macos-14 # for arm64 build
|
- os: macos-15 # for arm64 build
|
||||||
arch: arm64
|
arch: arm64
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
|
|||||||
@@ -124,3 +124,43 @@ export function checkAnyCombineBossRecommendFilterHasCondition(value) {
|
|||||||
return !!value[k]?.length
|
return !!value[k]?.length
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStaticCombineFilterKey(condition) {
|
||||||
|
const kAsO = {}
|
||||||
|
for (const key of Object.keys(condition ?? []).sort()) {
|
||||||
|
if (condition[key] === null || condition[key] === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kAsO[key] = condition[key]
|
||||||
|
}
|
||||||
|
return JSON.stringify(kAsO)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatStaticCombineFilters(rawStaticCombineRecommendJobFilterConditions) {
|
||||||
|
rawStaticCombineRecommendJobFilterConditions = JSON.parse(JSON.stringify(rawStaticCombineRecommendJobFilterConditions))
|
||||||
|
const map = new Map()
|
||||||
|
for (const condition of rawStaticCombineRecommendJobFilterConditions ?? []) {
|
||||||
|
const key = getStaticCombineFilterKey(condition)
|
||||||
|
map.set(key, condition)
|
||||||
|
}
|
||||||
|
const conditions = Array.from(map.values())
|
||||||
|
const result = conditions.map((condition) => {
|
||||||
|
return {
|
||||||
|
salaryList: condition.salary ? [condition.salary] : [],
|
||||||
|
experienceList: condition.experience ? [condition.experience] : [],
|
||||||
|
degreeList: condition.degree ? [condition.degree] : [],
|
||||||
|
scaleList: condition.scale ? [condition.scale] : [],
|
||||||
|
industryList: condition.industry ? [condition.industry] : []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!result.length) {
|
||||||
|
result.push({
|
||||||
|
salaryList: [],
|
||||||
|
experienceList: [],
|
||||||
|
degreeList: [],
|
||||||
|
scaleList: [],
|
||||||
|
industryList: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"combineRecommendJobFilterType": 1,
|
||||||
"anyCombineRecommendJobFilter": {
|
"anyCombineRecommendJobFilter": {
|
||||||
"salaryList": [],
|
"salaryList": [],
|
||||||
"experienceList": [],
|
"experienceList": [],
|
||||||
@@ -6,6 +7,7 @@
|
|||||||
"scaleList": [],
|
"scaleList": [],
|
||||||
"industryList": []
|
"industryList": []
|
||||||
},
|
},
|
||||||
|
"staticCombineRecommendJobFilterConditions": [],
|
||||||
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
|
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
|
||||||
"expectJobRegExpStr": "",
|
"expectJobRegExpStr": "",
|
||||||
"jobNotMatchStrategy": 1,
|
"jobNotMatchStrategy": 1,
|
||||||
@@ -29,5 +31,8 @@
|
|||||||
"type": "expect",
|
"type": "expect",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"isSageTimeEnabled": true,
|
||||||
|
"sageTimeOpTimes": 100,
|
||||||
|
"sageTimePauseMinute": 15
|
||||||
}
|
}
|
||||||
@@ -15,12 +15,21 @@ import { readConfigFile, writeStorageFile, ensureConfigFileExist, readStorageFil
|
|||||||
import {
|
import {
|
||||||
calculateTotalCombinations,
|
calculateTotalCombinations,
|
||||||
combineFiltersWithConstraintsGenerator,
|
combineFiltersWithConstraintsGenerator,
|
||||||
checkAnyCombineBossRecommendFilterHasCondition
|
checkAnyCombineBossRecommendFilterHasCondition,
|
||||||
|
formatStaticCombineFilters,
|
||||||
} from './combineCalculator.mjs'
|
} from './combineCalculator.mjs'
|
||||||
import { default as jobFilterConditions } from './internal-config/job-filter-conditions-20241002.json'
|
import { default as jobFilterConditions } from './internal-config/job-filter-conditions-20241002.json'
|
||||||
import { default as rawIndustryFilterExemption } from './internal-config/job-filter-industry-filter-exemption-20241002.json'
|
import { default as rawIndustryFilterExemption } from './internal-config/job-filter-industry-filter-exemption-20241002.json'
|
||||||
import { ChatStartupFrom } from '@geekgeekrun/sqlite-plugin/dist/entity/ChatStartupLog'
|
import { ChatStartupFrom } from '@geekgeekrun/sqlite-plugin/dist/entity/ChatStartupLog'
|
||||||
import { MarkAsNotSuitReason, MarkAsNotSuitOp, StrategyScopeOptionWhenMarkJobNotMatch, SalaryCalculateWay, JobDetailRegExpMatchLogic, JobSource } from '@geekgeekrun/sqlite-plugin/dist/enums'
|
import {
|
||||||
|
MarkAsNotSuitReason,
|
||||||
|
MarkAsNotSuitOp,
|
||||||
|
StrategyScopeOptionWhenMarkJobNotMatch,
|
||||||
|
SalaryCalculateWay,
|
||||||
|
JobDetailRegExpMatchLogic,
|
||||||
|
JobSource,
|
||||||
|
CombineRecommendJobFilterType
|
||||||
|
} from '@geekgeekrun/sqlite-plugin/dist/enums'
|
||||||
import {
|
import {
|
||||||
activeDescList,
|
activeDescList,
|
||||||
RECOMMEND_JOB_ENTRY_SELECTOR,
|
RECOMMEND_JOB_ENTRY_SELECTOR,
|
||||||
@@ -28,6 +37,7 @@ import {
|
|||||||
SEARCH_BOX_SELECTOR,
|
SEARCH_BOX_SELECTOR,
|
||||||
} from './constant.mjs'
|
} from './constant.mjs'
|
||||||
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
|
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
|
||||||
|
import { waitForSageTimeOrJustContinue } from './sage-time.mjs'
|
||||||
const jobFilterConditionsMapByCode = {}
|
const jobFilterConditionsMapByCode = {}
|
||||||
Object.values(jobFilterConditions).forEach(arr => {
|
Object.values(jobFilterConditions).forEach(arr => {
|
||||||
arr.forEach(option => {
|
arr.forEach(option => {
|
||||||
@@ -85,8 +95,10 @@ const bossCookies = readStorageFile('boss-cookies.json')
|
|||||||
const bossLocalStorage = readStorageFile('boss-local-storage.json')
|
const bossLocalStorage = readStorageFile('boss-local-storage.json')
|
||||||
|
|
||||||
const targetCompanyList = readConfigFile('target-company-list.json').filter(it => !!it.trim());
|
const targetCompanyList = readConfigFile('target-company-list.json').filter(it => !!it.trim());
|
||||||
|
const combineRecommendJobFilterType = readConfigFile('boss.json').combineRecommendJobFilterType ?? CombineRecommendJobFilterType.ANY_COMBINE
|
||||||
|
|
||||||
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
|
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
|
||||||
|
const staticCombineRecommendJobFilterConditions = readConfigFile('boss.json').staticCombineRecommendJobFilterConditions ?? []
|
||||||
let isSkipEmptyConditionForCombineRecommendJobFilter = readConfigFile('boss.json').isSkipEmptyConditionForCombineRecommendJobFilter
|
let isSkipEmptyConditionForCombineRecommendJobFilter = readConfigFile('boss.json').isSkipEmptyConditionForCombineRecommendJobFilter
|
||||||
if (!checkAnyCombineBossRecommendFilterHasCondition(anyCombineRecommendJobFilter)) {
|
if (!checkAnyCombineBossRecommendFilterHasCondition(anyCombineRecommendJobFilter)) {
|
||||||
isSkipEmptyConditionForCombineRecommendJobFilter = false
|
isSkipEmptyConditionForCombineRecommendJobFilter = false
|
||||||
@@ -108,7 +120,18 @@ const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
|
|||||||
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||||
|
|
||||||
// work exp
|
// work exp
|
||||||
const expectWorkExpList = readConfigFile('boss.json').expectWorkExpList ?? []
|
let expectWorkExpList = readConfigFile('boss.json').expectWorkExpList ?? []
|
||||||
|
const expectWorkExpListSet = new Set(expectWorkExpList)
|
||||||
|
if (
|
||||||
|
expectWorkExpListSet.has('应届生') ||
|
||||||
|
expectWorkExpListSet.has('在校生')
|
||||||
|
) {
|
||||||
|
expectWorkExpListSet.delete('应届生')
|
||||||
|
expectWorkExpListSet.delete('在校生')
|
||||||
|
expectWorkExpListSet.add('在校/应届')
|
||||||
|
}
|
||||||
|
expectWorkExpList = Array.from(expectWorkExpListSet)
|
||||||
|
|
||||||
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||||
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||||
|
|
||||||
@@ -271,8 +294,8 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
|||||||
case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT:
|
case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT:
|
||||||
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: {
|
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: {
|
||||||
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`))
|
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title$="距离远"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离远"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="公司不感兴趣"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
||||||
if (opProxy) {
|
if (opProxy) {
|
||||||
@@ -282,10 +305,10 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: {
|
case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: {
|
||||||
const opProxy = (await chooseReasonDialogProxy.$(`xpath///*[contains(@class,'zp-type-item')][contains(@title, "薪资")]`))
|
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title*="薪资"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title$="距离远"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离远"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="公司不感兴趣"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
||||||
if (opProxy) {
|
if (opProxy) {
|
||||||
@@ -296,11 +319,11 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
|||||||
}
|
}
|
||||||
case MarkAsNotSuitReason.JOB_NOT_SUIT:
|
case MarkAsNotSuitReason.JOB_NOT_SUIT:
|
||||||
default: {
|
default: {
|
||||||
const jobNotSuitOptionProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="职位"]`))
|
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="职位"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
||||||
if (jobNotSuitOptionProxy) {
|
if (opProxy) {
|
||||||
await jobNotSuitOptionProxy.click()
|
await opProxy.click()
|
||||||
isOptionChosen = true
|
isOptionChosen = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -347,24 +370,24 @@ export function testIfJobTitleOrDescriptionSuit (jobInfo, matchLogic) {
|
|||||||
let isJobNameSuit = matchLogic === JobDetailRegExpMatchLogic.SOME ? false : true
|
let isJobNameSuit = matchLogic === JobDetailRegExpMatchLogic.SOME ? false : true
|
||||||
try {
|
try {
|
||||||
if (expectJobNameRegExpStr.trim()) {
|
if (expectJobNameRegExpStr.trim()) {
|
||||||
const regExp = new RegExp(expectJobNameRegExpStr, 'i')
|
const regExp = new RegExp(expectJobNameRegExpStr, 'im')
|
||||||
isJobNameSuit = regExp.test(jobInfo.jobName)
|
isJobNameSuit = regExp.test(jobInfo.jobName?.replace(/\n/g, '') ?? '')
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
let isJobTypeSuit = matchLogic === JobDetailRegExpMatchLogic.SOME ? false : true
|
let isJobTypeSuit = matchLogic === JobDetailRegExpMatchLogic.SOME ? false : true
|
||||||
try {
|
try {
|
||||||
if (expectJobTypeRegExpStr.trim()) {
|
if (expectJobTypeRegExpStr.trim()) {
|
||||||
const regExp = new RegExp(expectJobTypeRegExpStr, 'i')
|
const regExp = new RegExp(expectJobTypeRegExpStr, 'im')
|
||||||
isJobTypeSuit = regExp.test(jobInfo.positionName)
|
isJobTypeSuit = regExp.test(jobInfo.positionName?.replace(/\n/g, '') ?? '')
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
let isJobDescSuit = matchLogic === JobDetailRegExpMatchLogic.SOME ? false : true
|
let isJobDescSuit = matchLogic === JobDetailRegExpMatchLogic.SOME ? false : true
|
||||||
try {
|
try {
|
||||||
if (expectJobDescRegExpStr.trim()) {
|
if (expectJobDescRegExpStr.trim()) {
|
||||||
const regExp = new RegExp(expectJobDescRegExpStr, 'i')
|
const regExp = new RegExp(expectJobDescRegExpStr, 'im')
|
||||||
isJobDescSuit = regExp.test(jobInfo.postDescription)
|
isJobDescSuit = regExp.test(jobInfo.postDescription?.replace(/\n/g, '') ?? '')
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
@@ -513,10 +536,11 @@ async function toRecommendPage (hooks) {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
await Promise.all([
|
page.goto(recommendJobPageUrl, { timeout: 1 * 1000 }).catch(e => { void e })
|
||||||
page.goto(recommendJobPageUrl, { timeout: 120 * 1000 }),
|
await sleep(3000)
|
||||||
page.waitForNavigation(),
|
await page.waitForFunction(() => {
|
||||||
])
|
return document.readyState === 'complete'
|
||||||
|
}, { timeout: 120 * 1000 })
|
||||||
if (
|
if (
|
||||||
page.url().startsWith('https://www.zhipin.com/web/common/403.html') ||
|
page.url().startsWith('https://www.zhipin.com/web/common/403.html') ||
|
||||||
page.url().startsWith('https://www.zhipin.com/web/common/error.html')
|
page.url().startsWith('https://www.zhipin.com/web/common/error.html')
|
||||||
@@ -651,11 +675,13 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterConditions =
|
||||||
|
combineRecommendJobFilterType === CombineRecommendJobFilterType.STATIC_COMBINE
|
||||||
|
? formatStaticCombineFilters(staticCombineRecommendJobFilterConditions)
|
||||||
|
: combineFiltersWithConstraintsGenerator(anyCombineRecommendJobFilter)
|
||||||
let expectJobList
|
let expectJobList
|
||||||
iterateFilterCondition: for (
|
iterateFilterCondition: for (
|
||||||
const filterCondition of combineFiltersWithConstraintsGenerator(
|
const filterCondition of filterConditions
|
||||||
anyCombineRecommendJobFilter
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
findInCurrentFilterCondition: while(true) {
|
findInCurrentFilterCondition: while(true) {
|
||||||
await sleepWithRandomDelay(2500)
|
await sleepWithRandomDelay(2500)
|
||||||
@@ -680,11 +706,17 @@ async function toRecommendPage (hooks) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
(
|
||||||
|
combineRecommendJobFilterType === CombineRecommendJobFilterType.STATIC_COMBINE && filterCondition === null
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
combineRecommendJobFilterType === CombineRecommendJobFilterType.ANY_COMBINE &&
|
||||||
isSkipEmptyConditionForCombineRecommendJobFilter &&
|
isSkipEmptyConditionForCombineRecommendJobFilter &&
|
||||||
Object.keys(filterCondition).length &&
|
Object.keys(filterCondition).length &&
|
||||||
Object.keys(filterCondition).every(k => !filterCondition[k]?.length)
|
Object.keys(filterCondition).every(k => !filterCondition[k]?.length)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
sleep(4000)
|
sleep(4000)
|
||||||
continue iterateFilterCondition
|
continue iterateFilterCondition
|
||||||
@@ -707,6 +739,10 @@ async function toRecommendPage (hooks) {
|
|||||||
);
|
);
|
||||||
await storeStorage(page).catch(() => void 0)
|
await storeStorage(page).catch(() => void 0)
|
||||||
await sleepWithRandomDelay(2000)
|
await sleepWithRandomDelay(2000)
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'afterJobSourceChosen',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await sleepWithRandomDelay(1500)
|
await sleepWithRandomDelay(1500)
|
||||||
await setFilterCondition(filterCondition)
|
await setFilterCondition(filterCondition)
|
||||||
@@ -807,7 +843,7 @@ async function toRecommendPage (hooks) {
|
|||||||
if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
|
if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (expectSalaryLow && salaryData.low < expectSalaryLow) {
|
if (expectSalaryLow && salaryData.high < expectSalaryLow) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
} else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||||
@@ -815,7 +851,7 @@ async function toRecommendPage (hooks) {
|
|||||||
if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
|
if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (expectSalaryLow && (salaryData.low * salaryDataMonth) / 10 < expectSalaryLow) {
|
if (expectSalaryLow && (salaryData.high * salaryDataMonth) / 10 < expectSalaryLow) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -900,7 +936,10 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
requestNextPagePromiseWithResolver = null
|
requestNextPagePromiseWithResolver = null
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'afterJobListPageFetched',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
await sleep(5000)
|
await sleep(5000)
|
||||||
await updateJobListData()
|
await updateJobListData()
|
||||||
tempTargetJobIndexToCheckDetail = getTempTargetJobIndexToCheckDetail()
|
tempTargetJobIndexToCheckDetail = getTempTargetJobIndexToCheckDetail()
|
||||||
@@ -944,6 +983,10 @@ async function toRecommendPage (hooks) {
|
|||||||
);
|
);
|
||||||
await sleepWithRandomDelay(2000)
|
await sleepWithRandomDelay(2000)
|
||||||
}
|
}
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'afterJobDetailFetched',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
targetJobData = await page.evaluate('document.querySelector(".job-detail-box").__vue__.data')
|
targetJobData = await page.evaluate('document.querySelector(".job-detail-box").__vue__.data')
|
||||||
selectedJobData = await page.evaluate('document.querySelector(".page-jobs-main").__vue__.currentJob')
|
selectedJobData = await page.evaluate('document.querySelector(".page-jobs-main").__vue__.currentJob')
|
||||||
// save the job detail info
|
// save the job detail info
|
||||||
@@ -971,6 +1014,10 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
else if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
else if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||||
try {
|
try {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobNotSuitMarked',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.BOSS_INACTIVE)
|
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.BOSS_INACTIVE)
|
||||||
await hooks.jobMarkedAsNotSuit.promise(
|
await hooks.jobMarkedAsNotSuit.promise(
|
||||||
targetJobData,
|
targetJobData,
|
||||||
@@ -985,7 +1032,8 @@ async function toRecommendPage (hooks) {
|
|||||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch {
|
} catch(err) {
|
||||||
|
console.log(`mark boss inactive failed`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1008,6 +1056,10 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
else if (expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
else if (expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||||
try {
|
try {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobNotSuitMarked',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_CITY_NOT_SUIT)
|
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_CITY_NOT_SUIT)
|
||||||
await hooks.jobMarkedAsNotSuit.promise(
|
await hooks.jobMarkedAsNotSuit.promise(
|
||||||
targetJobData,
|
targetJobData,
|
||||||
@@ -1021,7 +1073,8 @@ async function toRecommendPage (hooks) {
|
|||||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch {
|
} catch(err) {
|
||||||
|
console.log(`mark job city not suit failed`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1044,6 +1097,10 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
else if (expectWorkExpNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
else if (expectWorkExpNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||||
try {
|
try {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobNotSuitMarked',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT)
|
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT)
|
||||||
await hooks.jobMarkedAsNotSuit.promise(
|
await hooks.jobMarkedAsNotSuit.promise(
|
||||||
targetJobData,
|
targetJobData,
|
||||||
@@ -1057,7 +1114,8 @@ async function toRecommendPage (hooks) {
|
|||||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch {
|
} catch(err) {
|
||||||
|
console.log(`mark job work exp not suit failed`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1080,6 +1138,10 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
else if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
else if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||||
try {
|
try {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobNotSuitMarked',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_NOT_SUIT)
|
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_NOT_SUIT)
|
||||||
await hooks.jobMarkedAsNotSuit.promise(
|
await hooks.jobMarkedAsNotSuit.promise(
|
||||||
targetJobData,
|
targetJobData,
|
||||||
@@ -1094,7 +1156,8 @@ async function toRecommendPage (hooks) {
|
|||||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch {
|
} catch(err) {
|
||||||
|
console.log(`mark job detail not suit failed`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1119,6 +1182,10 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
else if (expectSalaryNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
else if (expectSalaryNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||||
try {
|
try {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobNotSuitMarked',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT)
|
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT)
|
||||||
await hooks.jobMarkedAsNotSuit.promise(
|
await hooks.jobMarkedAsNotSuit.promise(
|
||||||
targetJobData,
|
targetJobData,
|
||||||
@@ -1133,7 +1200,8 @@ async function toRecommendPage (hooks) {
|
|||||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch {
|
} catch(err) {
|
||||||
|
console.log(`mark job salary not suit failed`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1188,6 +1256,12 @@ async function toRecommendPage (hooks) {
|
|||||||
await notSuitConditionHandleMap[markOnLocalDbCondition]()
|
await notSuitConditionHandleMap[markOnLocalDbCondition]()
|
||||||
continue continueFind
|
continue continueFind
|
||||||
}
|
}
|
||||||
|
// 3.
|
||||||
|
const noOpCondition = Object.keys(notSuitReasonIdToStrategyMap).find(k => notSuitReasonIdToStrategyMap[k] === MarkAsNotSuitOp.NO_OP)
|
||||||
|
if (noOpCondition) {
|
||||||
|
await notSuitConditionHandleMap[noOpCondition]()
|
||||||
|
continue continueFind
|
||||||
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
if (
|
if (
|
||||||
// test company again - when allow list not include target company, just skip
|
// test company again - when allow list not include target company, just skip
|
||||||
@@ -1229,6 +1303,10 @@ async function toRecommendPage (hooks) {
|
|||||||
reject(err)
|
reject(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobChatStartup',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
await sleepWithRandomDelay(1000)
|
await sleepWithRandomDelay(1000)
|
||||||
const startChatButtonInnerHTML = await page.evaluate('document.querySelector(".job-detail-box .op-btn.op-btn-chat")?.innerHTML.trim()')
|
const startChatButtonInnerHTML = await page.evaluate('document.querySelector(".job-detail-box .op-btn.op-btn-chat")?.innerHTML.trim()')
|
||||||
|
|
||||||
@@ -1238,6 +1316,7 @@ async function toRecommendPage (hooks) {
|
|||||||
//#region click the chat button
|
//#region click the chat button
|
||||||
await startChatButtonProxy.click()
|
await startChatButtonProxy.click()
|
||||||
|
|
||||||
|
const waitAddFriendResponse = async () => {
|
||||||
const addFriendResponse = await page.waitForResponse(
|
const addFriendResponse = await page.waitForResponse(
|
||||||
response => {
|
response => {
|
||||||
if (
|
if (
|
||||||
@@ -1249,17 +1328,9 @@ async function toRecommendPage (hooks) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
const res = await addFriendResponse.json()
|
const res = await addFriendResponse.json()
|
||||||
|
return res
|
||||||
if (res.code !== 0) {
|
|
||||||
// startup chat error, may the chance of today has used out
|
|
||||||
if (res.zpData.bizCode === 1 && res.zpData.bizData?.chatRemindDialog?.blockLevel === 0 && res.zpData.bizData?.chatRemindDialog?.content === `今日沟通人数已达上限,请明天再试`) {
|
|
||||||
await storeStorage(page).catch(() => void 0)
|
|
||||||
throw new Error('STARTUP_CHAT_ERROR_DUE_TO_TODAY_CHANCE_HAS_USED_OUT')
|
|
||||||
} else {
|
|
||||||
console.error(res)
|
|
||||||
throw new Error('STARTUP_CHAT_ERROR_WITH_UNKNOWN_ERROR')
|
|
||||||
}
|
}
|
||||||
} else {
|
const waitAndHandleChatSuccess = async () => {
|
||||||
await hooks.newChatStartup?.promise(
|
await hooks.newChatStartup?.promise(
|
||||||
targetJobData,
|
targetJobData,
|
||||||
{
|
{
|
||||||
@@ -1275,6 +1346,58 @@ async function toRecommendPage (hooks) {
|
|||||||
await closeDialogButtonProxy.click()
|
await closeDialogButtonProxy.click()
|
||||||
await sleepWithRandomDelay(2000)
|
await sleepWithRandomDelay(2000)
|
||||||
}
|
}
|
||||||
|
const handleAddFriendResponse = async (res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
await waitAndHandleChatSuccess()
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
res.zpData.bizCode === 1 &&
|
||||||
|
res.zpData.bizData?.chatRemindDialog?.blockLevel === 0 &&
|
||||||
|
/剩\d+次沟通机会/.test(res.zpData.bizData?.chatRemindDialog?.content)
|
||||||
|
) {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobChatStartupAfterTwiceConfirm',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
|
const confirmButton = await page.waitForSelector('.chat-block-dialog .chat-block-footer .sure-btn')
|
||||||
|
await confirmButton.click()
|
||||||
|
const nextRes = await waitAddFriendResponse()
|
||||||
|
await handleAddFriendResponse(nextRes)
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
res.zpData.bizCode === 1 &&
|
||||||
|
/猎头/.test(res.zpData.bizData?.chatRemindDialog?.content)
|
||||||
|
) {
|
||||||
|
await waitForSageTimeOrJustContinue({
|
||||||
|
tag: 'beforeJobChatStartupAfterTwiceConfirm',
|
||||||
|
hooks
|
||||||
|
})
|
||||||
|
const confirmButton = await page.waitForSelector(`xpath///*[contains(@class, "chat-block-dialog")]//*[contains(@class, "chat-block-footer")]//*[contains(text(), "继续")]`)
|
||||||
|
await confirmButton.click()
|
||||||
|
const nextRes = await waitAddFriendResponse()
|
||||||
|
await handleAddFriendResponse(nextRes)
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
res.zpData.bizCode === 1 &&
|
||||||
|
res.zpData.bizData?.chatRemindDialog?.blockLevel === 0 &&
|
||||||
|
(
|
||||||
|
res.zpData.bizData?.chatRemindDialog?.content === `今日沟通人数已达上限,请明天再试` ||
|
||||||
|
/明天再来/.test(res.zpData.bizData?.chatRemindDialog?.content)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// startup chat error, may the chance of today has used out
|
||||||
|
await storeStorage(page).catch(() => void 0)
|
||||||
|
throw new Error('STARTUP_CHAT_ERROR_DUE_TO_TODAY_CHANCE_HAS_USED_OUT')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(
|
||||||
|
JSON.stringify(res, null, 2)
|
||||||
|
)
|
||||||
|
throw new Error('STARTUP_CHAT_ERROR_WITH_UNKNOWN_ERROR')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await waitAddFriendResponse()
|
||||||
|
await handleAddFriendResponse(res)
|
||||||
// #endregion
|
// #endregion
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { readConfigFile } from './runtime-file-utils.mjs'
|
||||||
|
import { sleep } from "@geekgeekrun/utils/sleep.mjs"
|
||||||
|
let {
|
||||||
|
isSageTimeEnabled,
|
||||||
|
sageTimeOpTimes,
|
||||||
|
sageTimePauseMinute,
|
||||||
|
} = readConfigFile('boss.json')
|
||||||
|
isSageTimeEnabled = isSageTimeEnabled ?? true
|
||||||
|
sageTimeOpTimes =
|
||||||
|
isNaN(parseInt(sageTimeOpTimes)) ||
|
||||||
|
parseInt(sageTimeOpTimes) < 1
|
||||||
|
? 100
|
||||||
|
: parseInt(sageTimeOpTimes)
|
||||||
|
sageTimePauseMinute =
|
||||||
|
isNaN(parseFloat(sageTimePauseMinute)) ||
|
||||||
|
parseFloat(sageTimePauseMinute) < 0
|
||||||
|
? 15
|
||||||
|
: parseFloat(sageTimePauseMinute)
|
||||||
|
if (parseFloat(sageTimePauseMinute) === 0) {
|
||||||
|
isSageTimeEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalEnabledTimes = 0
|
||||||
|
let recordedOpCount = 0
|
||||||
|
export const waitForSageTimeOrJustContinue = async ({
|
||||||
|
tag,
|
||||||
|
hooks,
|
||||||
|
} = {}) => {
|
||||||
|
if (!isSageTimeEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const tagText = tag ? ` ${tag}` : ''
|
||||||
|
if (recordedOpCount > sageTimeOpTimes) {
|
||||||
|
totalEnabledTimes++
|
||||||
|
console.log(`[SageTime${tagText}] 请求已达限制,开启;当前记录次数 ${recordedOpCount};第 ${totalEnabledTimes} 次开启`)
|
||||||
|
await hooks?.sageTimeEnter?.promise({
|
||||||
|
tag,
|
||||||
|
totalEnabledTimes,
|
||||||
|
recordedOpCount,
|
||||||
|
})
|
||||||
|
await sleep(sageTimePauseMinute * 60 * 1000)
|
||||||
|
console.log(`[SageTime${tagText}] 请求限制已解除,关闭;当前记录次数 ${recordedOpCount};第 ${totalEnabledTimes} 次关闭`)
|
||||||
|
await hooks?.sageTimeExit?.promise({
|
||||||
|
tag,
|
||||||
|
totalEnabledTimes,
|
||||||
|
recordedOpCount,
|
||||||
|
})
|
||||||
|
recordedOpCount = 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`[SageTime${tagText}] 请求未达限制;当前记录次数 ${recordedOpCount};已开启过 ${totalEnabledTimes} 次`)
|
||||||
|
recordedOpCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,7 +57,9 @@ const main = async () => {
|
|||||||
noPositionFoundForCurrentJob: new SyncHook(),
|
noPositionFoundForCurrentJob: new SyncHook(),
|
||||||
noPositionFoundAfterTraverseAllJob: new SyncHook(),
|
noPositionFoundAfterTraverseAllJob: new SyncHook(),
|
||||||
errorEncounter: new SyncHook(['errorInfo']),
|
errorEncounter: new SyncHook(['errorInfo']),
|
||||||
encounterEmptyRecommendJobList: new AsyncSeriesHook(['args'])
|
encounterEmptyRecommendJobList: new AsyncSeriesHook(['args']),
|
||||||
|
sageTimeEnter: new AsyncSeriesHook(['args']),
|
||||||
|
sageTimeExit: new AsyncSeriesHook(['args'])
|
||||||
}
|
}
|
||||||
initPlugins(hooks)
|
initPlugins(hooks)
|
||||||
await hooks.daemonInitialized.callAsync()
|
await hooks.daemonInitialized.callAsync()
|
||||||
|
|||||||
@@ -34,3 +34,8 @@ export enum JobSource {
|
|||||||
recommend = 2,
|
recommend = 2,
|
||||||
search = 3,
|
search = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CombineRecommendJobFilterType {
|
||||||
|
ANY_COMBINE = 1,
|
||||||
|
STATIC_COMBINE = 2,
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { defineConfig, externalizeDepsPlugin, loadEnv } from 'electron-vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import UnoCSS from 'unocss/vite'
|
import UnoCSS from 'unocss/vite'
|
||||||
import { presetUno, presetAttributify, presetIcons } from 'unocss'
|
import { presetUno, presetAttributify, presetIcons } from 'unocss'
|
||||||
import transformerDirective from "@unocss/transformer-directives";
|
import transformerDirective from '@unocss/transformer-directives'
|
||||||
import Replace from 'unplugin-replace/vite'
|
import Replace from 'unplugin-replace/vite'
|
||||||
|
|
||||||
process.env = { ...process.env, ...loadEnv(process.env.NODE_ENV!, process.cwd()) }
|
process.env = { ...process.env, ...loadEnv(process.env.NODE_ENV!, process.cwd()) }
|
||||||
@@ -13,11 +13,17 @@ export default defineConfig({
|
|||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: []
|
external: []
|
||||||
},
|
},
|
||||||
minify: 'terser'
|
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
externalizeDepsPlugin({
|
externalizeDepsPlugin({
|
||||||
exclude: ['@geekgeekrun/geek-auto-start-chat-with-boss', '@geekgeekrun/dingtalk-plugin', '@geekgeekrun/utils', 'find-chrome-bin', '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension']
|
exclude: [
|
||||||
|
'@geekgeekrun/geek-auto-start-chat-with-boss',
|
||||||
|
'@geekgeekrun/dingtalk-plugin',
|
||||||
|
'@geekgeekrun/utils',
|
||||||
|
'find-chrome-bin',
|
||||||
|
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension'
|
||||||
|
]
|
||||||
}),
|
}),
|
||||||
Replace({
|
Replace({
|
||||||
delimiters: ['', ''],
|
delimiters: ['', ''],
|
||||||
@@ -26,11 +32,11 @@ export default defineConfig({
|
|||||||
values: [
|
values: [
|
||||||
{
|
{
|
||||||
find: /<measurement_id>/g,
|
find: /<measurement_id>/g,
|
||||||
replacement: process.env.VITE_APP_GTAG_MEASUREMENT_ID as string,
|
replacement: process.env.VITE_APP_GTAG_MEASUREMENT_ID as string
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: /<api_secret>/g,
|
find: /<api_secret>/g,
|
||||||
replacement: process.env.VITE_APP_GTAG_API_SECRET as string,
|
replacement: process.env.VITE_APP_GTAG_API_SECRET as string
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -39,7 +45,7 @@ export default defineConfig({
|
|||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
build: {
|
build: {
|
||||||
minify: 'terser'
|
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
@@ -52,11 +58,11 @@ export default defineConfig({
|
|||||||
vue(),
|
vue(),
|
||||||
UnoCSS({
|
UnoCSS({
|
||||||
presets: [presetUno(), presetAttributify(), presetIcons()],
|
presets: [presetUno(), presetAttributify(), presetIcons()],
|
||||||
transformers: [transformerDirective()],
|
transformers: [transformerDirective()]
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
minify: 'terser'
|
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "geekgeekrun-ui",
|
"name": "geekgeekrun-ui",
|
||||||
"version": "0.9.2",
|
"version": "0.11.0",
|
||||||
"description": "Boss 炸弹 - 自动开聊Boss,助力每位打工人求职!",
|
"description": "Boss 炸弹 - 自动开聊Boss,助力每位打工人求职!",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "geekgeekrun",
|
"author": "geekgeekrun",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "0.9.2",
|
"version": "0.11.0",
|
||||||
"buildVersion": 18,
|
"buildVersion": 25,
|
||||||
"buildTime": 1755400602067,
|
"buildTime": 1765792961398,
|
||||||
"buildHash": "a25b047f99f7b1ac0c8ee55f1e121991e1dd6402",
|
"buildHash": "2b4b62097730bf0388f2179c02fdead281ec26fc",
|
||||||
"name": "geekgeekrun-ui"
|
"name": "geekgeekrun-ui"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import DingtalkPlugin from '@geekgeekrun/dingtalk-plugin/index.mjs'
|
import DingtalkPlugin from '@geekgeekrun/dingtalk-plugin/index.mjs'
|
||||||
import { app } from 'electron'
|
import { app, dialog } from 'electron'
|
||||||
import { SyncHook, AsyncSeriesHook } from 'tapable'
|
import { SyncHook, AsyncSeriesHook } from 'tapable'
|
||||||
import {
|
import {
|
||||||
readConfigFile,
|
readConfigFile,
|
||||||
@@ -94,7 +94,9 @@ const runAutoChat = async () => {
|
|||||||
noPositionFoundForCurrentJob: new SyncHook(),
|
noPositionFoundForCurrentJob: new SyncHook(),
|
||||||
noPositionFoundAfterTraverseAllJob: new SyncHook(),
|
noPositionFoundAfterTraverseAllJob: new SyncHook(),
|
||||||
errorEncounter: new SyncHook(['errorInfo']),
|
errorEncounter: new SyncHook(['errorInfo']),
|
||||||
encounterEmptyRecommendJobList: new AsyncSeriesHook(['args'])
|
encounterEmptyRecommendJobList: new AsyncSeriesHook(['args']),
|
||||||
|
sageTimeEnter: new AsyncSeriesHook(['args']),
|
||||||
|
sageTimeExit: new AsyncSeriesHook(['args'])
|
||||||
}
|
}
|
||||||
initPlugins(hooks)
|
initPlugins(hooks)
|
||||||
|
|
||||||
@@ -121,6 +123,11 @@ const runAutoChat = async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
if (err.message.includes('LOGIN_STATUS_INVALID')) {
|
if (err.message.includes('LOGIN_STATUS_INVALID')) {
|
||||||
|
await dialog.showMessageBox({
|
||||||
|
type: `error`,
|
||||||
|
message: `登录状态无效`,
|
||||||
|
detail: `请重新登录Boss直聘`
|
||||||
|
})
|
||||||
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
|
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,22 @@ export default function initIpc() {
|
|||||||
if (hasOwn(payload, 'jobSourceList')) {
|
if (hasOwn(payload, 'jobSourceList')) {
|
||||||
bossConfig.jobSourceList = payload.jobSourceList
|
bossConfig.jobSourceList = payload.jobSourceList
|
||||||
}
|
}
|
||||||
|
if (hasOwn(payload, 'combineRecommendJobFilterType')) {
|
||||||
|
bossConfig.combineRecommendJobFilterType = payload.combineRecommendJobFilterType
|
||||||
|
}
|
||||||
|
if (hasOwn(payload, 'staticCombineRecommendJobFilterConditions')) {
|
||||||
|
bossConfig.staticCombineRecommendJobFilterConditions =
|
||||||
|
payload.staticCombineRecommendJobFilterConditions
|
||||||
|
}
|
||||||
|
if (hasOwn(payload, 'isSageTimeEnabled')) {
|
||||||
|
bossConfig.isSageTimeEnabled = payload.isSageTimeEnabled
|
||||||
|
}
|
||||||
|
if (hasOwn(payload, 'sageTimeOpTimes')) {
|
||||||
|
bossConfig.sageTimeOpTimes = payload.sageTimeOpTimes
|
||||||
|
}
|
||||||
|
if (hasOwn(payload, 'sageTimePauseMinute')) {
|
||||||
|
bossConfig.sageTimePauseMinute = payload.sageTimePauseMinute
|
||||||
|
}
|
||||||
|
|
||||||
promiseArr.push(writeConfigFile('boss.json', bossConfig))
|
promiseArr.push(writeConfigFile('boss.json', bossConfig))
|
||||||
|
|
||||||
|
|||||||
@@ -31,5 +31,11 @@ export default class GtagPlugin {
|
|||||||
hooks.encounterEmptyRecommendJobList.tap('GtagPlugin', ({ pageQuery }) => {
|
hooks.encounterEmptyRecommendJobList.tap('GtagPlugin', ({ pageQuery }) => {
|
||||||
gtag('encounter_empty_rec_job_list', { pageQuery })
|
gtag('encounter_empty_rec_job_list', { pageQuery })
|
||||||
})
|
})
|
||||||
|
hooks.sageTimeEnter.tap('GtagPlugin', ({ tag, totalEnabledTimes, recordedOpCount }) => {
|
||||||
|
gtag('sage_time_enter', { tag, totalEnabledTimes, recordedOpCount })
|
||||||
|
})
|
||||||
|
hooks.sageTimeExit.tap('GtagPlugin', ({ tag, totalEnabledTimes, recordedOpCount }) => {
|
||||||
|
gtag('sage_time_exit', { tag, totalEnabledTimes, recordedOpCount })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-button p-0 h-auto flex size="small" type="text" :icon="Plus" @click="addCondition"
|
||||||
|
>添加条件</el-button
|
||||||
|
>
|
||||||
|
<div class="job-combo-filter" mt-8px>
|
||||||
|
<el-table
|
||||||
|
size="small"
|
||||||
|
:data="props.modelValue"
|
||||||
|
border
|
||||||
|
:style="{ maxWidth: '100%' }"
|
||||||
|
:row-style="
|
||||||
|
({ row }) => {
|
||||||
|
return {
|
||||||
|
backgroundColor:
|
||||||
|
duplicatedMap.get(getStaticCombineFilterKey(row))?.length > 1
|
||||||
|
? '#fcd4b7'
|
||||||
|
: 'transparent'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<div lh-1.5em>
|
||||||
|
列表中没有条件,将仅使用默认的“初始空条件”为您筛选职位<br />
|
||||||
|
你可以点击表格左上角“<el-button
|
||||||
|
p-0
|
||||||
|
h-auto
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
:icon="Plus"
|
||||||
|
@click="addCondition"
|
||||||
|
>添加条件</el-button
|
||||||
|
>”按钮,添加更多筛选条件。
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table-column :resizable="false" label="" :width="80">
|
||||||
|
<template #default="{ $index: index }">
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '2px',
|
||||||
|
height: 'fit-content'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
:disabled="index <= 0"
|
||||||
|
style="margin: 0"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
:icon="ArrowUp"
|
||||||
|
@click="moveConditionUp(index)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
:disabled="index >= modelValue?.length - 1"
|
||||||
|
style="margin: 0"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
:icon="ArrowDown"
|
||||||
|
@click="moveConditionDown(index)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
style="margin: 0"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
:icon="Delete"
|
||||||
|
@click="removeCondition(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :resizable="false" label="薪资待遇" prop="salary">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="row.salary"
|
||||||
|
:disabled="row.___itemType === 'empty-condition-placeholder'"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="it in conditions.salaryList.filter((it) => it.code !== 0)"
|
||||||
|
:key="it.code"
|
||||||
|
:value="it.code"
|
||||||
|
:label="it.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :resizable="false" label="工作经验" prop="experience">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="row.experience"
|
||||||
|
:disabled="row.___itemType === 'empty-condition-placeholder'"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="it in conditions.experienceList.filter((it) => it.code !== 0)"
|
||||||
|
:key="it.code"
|
||||||
|
:value="it.code"
|
||||||
|
:label="it.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :resizable="false" label="学历要求" prop="degree">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="row.degree"
|
||||||
|
:disabled="row.___itemType === 'empty-condition-placeholder'"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="it in conditions.degreeList.filter((it) => it.code !== 0)"
|
||||||
|
:key="it.code"
|
||||||
|
:value="it.code"
|
||||||
|
:label="it.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :resizable="false" label="公司行业" :width="200" prop="industry">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="row.industry"
|
||||||
|
:disabled="row.___itemType === 'empty-condition-placeholder'"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-option-group
|
||||||
|
v-for="group in industryFilterExemption"
|
||||||
|
:key="group.code"
|
||||||
|
:label="group.name"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in group.subLevelModelList"
|
||||||
|
:key="item.code"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.code"
|
||||||
|
/>
|
||||||
|
</el-option-group>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :resizable="false" label="公司规模" prop="scale">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="row.scale"
|
||||||
|
:disabled="row.___itemType === 'empty-condition-placeholder'"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="it in conditions.scaleList.filter((it) => it.code !== 0)"
|
||||||
|
:key="it.code"
|
||||||
|
:value="it.code"
|
||||||
|
:label="it.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
Array.from(duplicatedMap.values()).some((it) => {
|
||||||
|
return it.length > 1
|
||||||
|
})
|
||||||
|
"
|
||||||
|
color-orange
|
||||||
|
font-size-12px
|
||||||
|
>
|
||||||
|
列表中被橙色高亮的条件存在重复项,相关重复项将被合并,运行时遍历顺序以第一次出现为准
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import conditions from '@geekgeekrun/geek-auto-start-chat-with-boss/internal-config/job-filter-conditions-20241002.json'
|
||||||
|
import industryFilterExemption from '@geekgeekrun/geek-auto-start-chat-with-boss/internal-config/job-filter-industry-filter-exemption-20241002.json'
|
||||||
|
import { ArrowUp, ArrowDown, Delete, Plus } from '@element-plus/icons-vue'
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
|
|
||||||
|
import { getStaticCombineFilterKey } from '@geekgeekrun/geek-auto-start-chat-with-boss/combineCalculator.mjs'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array as PropType<
|
||||||
|
Array<{
|
||||||
|
salary: number | null
|
||||||
|
experience: number | null
|
||||||
|
degree: number | null
|
||||||
|
industry: number | null
|
||||||
|
scale: number | null
|
||||||
|
}>
|
||||||
|
>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
isSkipEmptyConditionForCombineRecommendJobFilter: {
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// fix for misspell of scale property.
|
||||||
|
for (const condition of props.modelValue) {
|
||||||
|
if ((condition as any).scaleList && !condition.scale) {
|
||||||
|
condition.scale = (condition as any).scaleList
|
||||||
|
delete (condition as any).scaleList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewConditionItem() {
|
||||||
|
return {
|
||||||
|
salary: null,
|
||||||
|
experience: null,
|
||||||
|
degree: null,
|
||||||
|
industry: null,
|
||||||
|
scale: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCondition() {
|
||||||
|
props.modelValue?.push(getNewConditionItem())
|
||||||
|
// gtagRenderer('resume_work_exp_added')
|
||||||
|
}
|
||||||
|
function moveConditionUp(index) {
|
||||||
|
;[props.modelValue[index], props.modelValue[index - 1]] = [
|
||||||
|
props.modelValue[index - 1],
|
||||||
|
props.modelValue[index]
|
||||||
|
]
|
||||||
|
// gtagRenderer('resume_work_exp_moved_up')
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveConditionDown(index) {
|
||||||
|
;[props.modelValue[index], props.modelValue[index + 1]] = [
|
||||||
|
props.modelValue[index + 1],
|
||||||
|
props.modelValue[index]
|
||||||
|
]
|
||||||
|
// gtagRenderer('resume_work_exp_moved_down')
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCondition(index) {
|
||||||
|
props.modelValue?.splice(index, 1)
|
||||||
|
// gtagRenderer('resume_work_exp_removed')
|
||||||
|
}
|
||||||
|
const duplicatedMap = computed(() => {
|
||||||
|
const map = new Map()
|
||||||
|
for (const condition of props.modelValue ?? []) {
|
||||||
|
const key = getStaticCombineFilterKey(condition)
|
||||||
|
if (!map.has(key)) {
|
||||||
|
map.set(key, [])
|
||||||
|
}
|
||||||
|
const arr = map.get(key)
|
||||||
|
arr.push(condition)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.job-combo-filter {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
.filter-item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
import { JobDetailRegExpMatchLogic } from '@geekgeekrun/sqlite-plugin/src/enums'
|
||||||
|
|
||||||
|
const expectJobFilterTemplateList = [
|
||||||
|
{
|
||||||
|
type: '不限职位',
|
||||||
|
name: '不限职位(随便投)',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: 'Java',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '\\bJava\\b',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'\\bJava\\b|JVM|消息队列|MQ|SQL|Oracle|MongoDB|Redis|Nginx|Dubbo|Docker|K8s|Kubernetes',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: 'Golang',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '\\bGolang\\b',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'\\bGo\\b|\\bGolang\\b|消息队列|MQ|SQL|Oracle|MongoDB|Redis|Nginx|Dubbo|Docker|K8s|Kubernetes',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: '前端开发工程师',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '前端|H5|\\bFE\\b',
|
||||||
|
expectJobTypeRegExpStr: '前端开发|javascript',
|
||||||
|
expectJobDescRegExpStr: '前端|vue|react|node|\\bjs\\b|javascript|H5',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: '前端开发工程师(不考虑外包、兼职)',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '^(?=.*(前端|H5|\\bFE\\b))(?!.*(?:外包|驻场|外派|兼职|短期))',
|
||||||
|
expectJobTypeRegExpStr: '前端开发|javascript',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'^(?=.*(前端|vue|react|node|\\bjs\\b|javascript|H5))(?!.*(?:外包|驻场|外派|兼职|短期))',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: '测试工程师、测试开发',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '测试|测开|QA|质量',
|
||||||
|
expectJobTypeRegExpStr: '测试工程师|测试开发',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'测试|测开|QA|线上问题|自动化|复盘|效率|Selenium|Puppeteer|Playwright|Cypress|JMeter|LoadRunner|QTP|TestNG|JUnit|Pytest|Fiddler|Charles|Jenkins|Appium|黑盒|白盒|用例|缺陷|Linux|Ubuntu|Debian|CentOS|Shell|c\\+\\+|Python|PHP|\\bJava\\b|Node|\\bGo\\b|\\bGolang\\b',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: '运维工程师、运维开发工程师',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '运维(开发)?|SRE',
|
||||||
|
expectJobTypeRegExpStr: '运维(开发)?工程师',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'运维|SRE|服务器|云计算|Docker|K8s|Kubernetes|Linux|Ubuntu|Debian|CentOS|Shell|Python|\\bGo\\b|\\bGolang\\b|监控|Prometheus|Grafana|ELK|负载均衡|部署|Nginx|Apache|DevOps|harbor',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: '数据开发',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '数据开发',
|
||||||
|
expectJobDescRegExpStr: 'c\\+\\+|Python|\\bGo\\b|\\bGolang\\b|\\bJava\\b|Node|数据仓库|ETL|大数据|Hadoop|Spark|Flink|Hive|Presto|数据湖|数仓|SQL|Oracle|MongoDB|Redis|Kafka',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '互联网/AI',
|
||||||
|
name: '实施工程师',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '实施',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '产品',
|
||||||
|
name: '产品经理',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '产品经理',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '产品',
|
||||||
|
name: '用户研究',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '用户研究',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '产品',
|
||||||
|
name: '游戏策划',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '游戏策划',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '客服/运营',
|
||||||
|
name: '产品运营',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '产品运营',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '客服/运营',
|
||||||
|
name: '用户运营',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '用户运营',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '客服/运营',
|
||||||
|
name: '数据标注/AI训练师',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '数据标注|AI训练师',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '财务/审计/税务',
|
||||||
|
name: '会计、出纳',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '会计|Accountant|出纳|财务',
|
||||||
|
expectJobTypeRegExpStr: '会计|出纳',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'会计|财务|出纳|审计|账务|税务|总账|做账|应付|应收|成本|资产|资金|记账|发票|结算|核算|汇算|利润|对账|报税|回款|SAP|用友|金蝶',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '人力/行政/法务',
|
||||||
|
name: '人力资源专员/助理、人力资源经理/主管',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: 'HR|人力|人资|人事',
|
||||||
|
expectJobTypeRegExpStr: '人力',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '人力/行政/法务',
|
||||||
|
name: 'HRBP',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: 'BP|HRG|HR|人力|人资|人事',
|
||||||
|
expectJobTypeRegExpStr: 'HRBP|人力',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '人力/行政/法务',
|
||||||
|
name: '员工关系',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '员工关系|劳动关系|SSC|社保|HR|人力|人资|人事',
|
||||||
|
expectJobTypeRegExpStr: '员工关系|人力',
|
||||||
|
expectJobDescRegExpStr: '员工关系|劳动关系|SSC|社保|考勤|入职|离职|入转调离',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '人力/行政/法务',
|
||||||
|
name: '招聘',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '招聘|高招|Recruiter|HR|人力|人资|人事',
|
||||||
|
expectJobTypeRegExpStr: '招聘|猎头|人力',
|
||||||
|
expectJobDescRegExpStr:
|
||||||
|
'招聘|高招|Recruiter|简历|面试|人才引进|Mapping|人才画像|offer|猎头|内推|外推|猎聘|Boss|拉勾|前程无忧|智联|58同城|领英|LinkedIn|ATS|人才库|Moka|北森|iTenant|倍罗|大易',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '人力/行政/法务',
|
||||||
|
name: '薪酬绩效',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '薪酬|绩效|福利|COE|payroll',
|
||||||
|
expectJobTypeRegExpStr: '薪酬绩效',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '人力/行政/法务',
|
||||||
|
name: '企业文化',
|
||||||
|
config: {
|
||||||
|
expectJobNameRegExpStr: '企业文化|组织文化|组织|OC|廉洁|反腐',
|
||||||
|
expectJobTypeRegExpStr: '企业文化',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default expectJobFilterTemplateList
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="form-wrap geek-auto-start-run-with-boss">
|
<div class="form-wrap geek-auto-start-run-with-boss">
|
||||||
<el-form ref="formRef" :model="formContent" label-position="top" :rules="formRules">
|
<el-form ref="formRef" :model="formContent" label-position="top" :rules="formRules">
|
||||||
<el-card class="config-section">
|
<el-card class="config-section">
|
||||||
<el-form-item mb0>
|
<el-form-item>
|
||||||
<div>
|
<div>
|
||||||
<div font-size-16px>BOSS直聘 Cookie</div>
|
<div font-size-16px>BOSS直聘 Cookie</div>
|
||||||
<el-button size="small" type="primary" @click="handleClickLaunchLogin"
|
<el-button size="small" type="primary" @click="handleClickLaunchLogin"
|
||||||
@@ -16,13 +16,107 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<div>
|
||||||
|
<div font-size-16px>
|
||||||
|
摸鱼模式
|
||||||
|
<el-tooltip
|
||||||
|
effect="light"
|
||||||
|
placement="bottom-start"
|
||||||
|
@show="gtagRenderer('tooltip_show_about_sage_t')"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div>
|
||||||
|
本程序运行较长时间后,Boss直聘会对账号进行风控,导致本程序不能继续执行。<br />
|
||||||
|
为此,加入摸鱼模式。通过此配置,主动减慢本程序的运行速度,降低潜在的被风控监测到的概率。<br />
|
||||||
|
你可以自定义开启摸鱼模式的频率 - 默认为操作100次后,暂停运行15分钟。
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-button type="text" font-size-12px
|
||||||
|
><span><QuestionFilled w-1em h-1em mr2px /></span
|
||||||
|
>这个配置会对本程序运行过程造成什么影响?</el-button
|
||||||
|
>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="formContent.isSageTimeEnabled"
|
||||||
|
@change="
|
||||||
|
(v) => {
|
||||||
|
gtagRenderer('sage_t_enable_changed', { v })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
启用摸鱼模式
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<div pl-1.5em font-size-14px>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
color: formContent.isSageTimeEnabled ? '' : '#aaa'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
当如下行为的次数总计达
|
||||||
|
<el-form-item mb0 inline-block prop="sageTimeOpTimes">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formContent.sageTimeOpTimes"
|
||||||
|
:step="1"
|
||||||
|
step-strictly
|
||||||
|
:precision="0"
|
||||||
|
:min="1"
|
||||||
|
:disabled="!formContent.isSageTimeEnabled"
|
||||||
|
controls-position="right"
|
||||||
|
@change="
|
||||||
|
(v) => {
|
||||||
|
gtagRenderer('sage_t_op_times_changed', { v })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
次时,暂停运行
|
||||||
|
<el-form-item mb0 inline-block prop="sageTimePauseMinute">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formContent.sageTimePauseMinute"
|
||||||
|
:step="0.5"
|
||||||
|
step-strictly
|
||||||
|
:precision="1"
|
||||||
|
:min="0"
|
||||||
|
:disabled="!formContent.isSageTimeEnabled"
|
||||||
|
controls-position="right"
|
||||||
|
@change="
|
||||||
|
(v) => {
|
||||||
|
gtagRenderer('sage_t_pause_minute_changed', { v })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
分钟:
|
||||||
|
<ul
|
||||||
|
pl-1em
|
||||||
|
mb0
|
||||||
|
mt14px
|
||||||
|
:style="{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr 1fr',
|
||||||
|
lineHeight: '1.5em'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<li>职位来源变更</li>
|
||||||
|
<li>职位列表滚动后发生加载</li>
|
||||||
|
<li>职位详情加载</li>
|
||||||
|
<li>职位被标记不合适</li>
|
||||||
|
<li>职位被开聊</li>
|
||||||
|
</ul>
|
||||||
|
<div mt14px>之后继续运行并重新计次,循环整个过程</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="config-section">
|
<el-card class="config-section">
|
||||||
<el-form-item class="job-source-form-item" prop="__jobSourceList">
|
<el-form-item class="job-source-form-item" prop="__jobSourceList">
|
||||||
<div w-full>
|
<div w-full>
|
||||||
<div ref="jobSourceFormItemSectionEl" font-size-16px>
|
<div ref="jobSourceFormItemSectionEl" font-size-16px>
|
||||||
<div>
|
<div>
|
||||||
职位来源及其查找顺序
|
你想投递Boss直聘上哪些列表里的职位?
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
effect="light"
|
effect="light"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
@@ -41,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div font-size-12px>
|
<div font-size-12px>
|
||||||
拖放条目前方的手柄以调整职位来源查找顺序;点击条目前方的开关以启用/禁用对应的职位来源
|
拖放条目前方的手柄以调整职位列表查找顺序;点击条目前方的开关以启用/禁用对应的职位列表
|
||||||
</div>
|
</div>
|
||||||
<JobSourceDragOrderer
|
<JobSourceDragOrderer
|
||||||
v-model="formContent.__jobSourceList"
|
v-model="formContent.__jobSourceList"
|
||||||
@@ -49,11 +143,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
<el-form-item prop="filter" mt18px mb0px w-full>
|
||||||
<el-card class="config-section">
|
<div flex-1>
|
||||||
<el-form-item prop="filter">
|
|
||||||
<div font-size-16px>
|
<div font-size-16px>
|
||||||
职位筛选条件
|
你希望Boss直聘为你筛选出什么样的职位?
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
effect="light"
|
effect="light"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
@@ -76,9 +169,43 @@
|
|||||||
>
|
>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<AnyCombineBossRecommendFilter v-model="formContent.anyCombineRecommendJobFilter" />
|
<div>
|
||||||
</el-form-item>
|
<div>
|
||||||
<el-form-item prop="filter" mb0>
|
<div font-size-12px>筛选条件遍历方式</div>
|
||||||
|
<el-select
|
||||||
|
v-model="formContent.combineRecommendJobFilterType"
|
||||||
|
w-320px
|
||||||
|
@change="
|
||||||
|
(v) => {
|
||||||
|
gtagRenderer('crjf_type_changed', { v })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="op in combineRecommendJobFilterTypeOptions"
|
||||||
|
:key="op.value"
|
||||||
|
:value="op.value"
|
||||||
|
:label="op.name"
|
||||||
|
>{{ op.name }}</el-option
|
||||||
|
>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
formContent.combineRecommendJobFilterType ===
|
||||||
|
CombineRecommendJobFilterType.STATIC_COMBINE
|
||||||
|
"
|
||||||
|
mt8px
|
||||||
|
>
|
||||||
|
<StaticCombineBossRecommendFilter
|
||||||
|
v-model="formContent.staticCombineRecommendJobFilterConditions"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else mt8px>
|
||||||
|
<AnyCombineBossRecommendFilter
|
||||||
|
v-model="formContent.anyCombineRecommendJobFilter"
|
||||||
|
/>
|
||||||
|
<div mb0>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-if="anyCombineBossRecommendFilterHasCondition"
|
v-if="anyCombineBossRecommendFilterHasCondition"
|
||||||
v-model="formContent.isSkipEmptyConditionForCombineRecommendJobFilter"
|
v-model="formContent.isSkipEmptyConditionForCombineRecommendJobFilter"
|
||||||
@@ -93,8 +220,10 @@
|
|||||||
<el-checkbox v-else :model-value="false" disabled>
|
<el-checkbox v-else :model-value="false" disabled>
|
||||||
<span font-size-12px>跳过初始空条件,直接使用设置的条件查找职位</span>
|
<span font-size-12px>跳过初始空条件,直接使用设置的条件查找职位</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-form-item>
|
</div>
|
||||||
<div font-size-12px mt10px>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div font-size-12px>
|
||||||
当前组合条件数:{{
|
当前组合条件数:{{
|
||||||
currentAnyCombineRecommendJobFilterCombinationCount.toLocaleString()
|
currentAnyCombineRecommendJobFilterCombinationCount.toLocaleString()
|
||||||
}}
|
}}
|
||||||
@@ -105,6 +234,8 @@
|
|||||||
否则将在当前职位中尝试太多筛选条件,不能及时进入下一个职位,且会增加命中风控的概率</span
|
否则将在当前职位中尝试太多筛选条件,不能及时进入下一个职位,且会增加命中风控的概率</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
<!-- <el-form-item
|
<!-- <el-form-item
|
||||||
label="钉钉机器人 AccessToken(用于记录开聊,请勿使用公司内部群)"
|
label="钉钉机器人 AccessToken(用于记录开聊,请勿使用公司内部群)"
|
||||||
@@ -582,9 +713,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
<el-form-item mb0 mt20px>
|
||||||
<el-card class="config-section">
|
|
||||||
<el-form-item mb0>
|
|
||||||
<div font-size-16px>职位详情筛选条件</div>
|
<div font-size-16px>职位详情筛选条件</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div>
|
<div>
|
||||||
@@ -617,7 +746,12 @@
|
|||||||
<el-icon class="el-icon--right"><arrow-down /></el-icon
|
<el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
></el-button>
|
></el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu
|
||||||
|
:style="{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr 1fr'
|
||||||
|
}"
|
||||||
|
>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
v-for="item in expectJobFilterTemplateList"
|
v-for="item in expectJobFilterTemplateList"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
@@ -925,15 +1059,18 @@ import { ElForm, ElMessage } from 'element-plus'
|
|||||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import AnyCombineBossRecommendFilter from '@renderer/features/AnyCombineBossRecommendFilter/index.vue'
|
import AnyCombineBossRecommendFilter from '@renderer/features/AnyCombineBossRecommendFilter/index.vue'
|
||||||
|
import StaticCombineBossRecommendFilter from '@renderer/features/StaticCombineBossRecommendFilter/index.vue'
|
||||||
import { activeDescList } from '@geekgeekrun/geek-auto-start-chat-with-boss/constant.mjs'
|
import { activeDescList } from '@geekgeekrun/geek-auto-start-chat-with-boss/constant.mjs'
|
||||||
import {
|
import {
|
||||||
calculateTotalCombinations,
|
calculateTotalCombinations,
|
||||||
checkAnyCombineBossRecommendFilterHasCondition
|
checkAnyCombineBossRecommendFilterHasCondition,
|
||||||
|
formatStaticCombineFilters
|
||||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/combineCalculator.mjs'
|
} from '@geekgeekrun/geek-auto-start-chat-with-boss/combineCalculator.mjs'
|
||||||
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||||
import defaultTargetCompanyListConf from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json'
|
import defaultTargetCompanyListConf from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json'
|
||||||
import { ArrowDown } from '@element-plus/icons-vue'
|
import { ArrowDown } from '@element-plus/icons-vue'
|
||||||
import {
|
import {
|
||||||
|
CombineRecommendJobFilterType,
|
||||||
MarkAsNotSuitOp,
|
MarkAsNotSuitOp,
|
||||||
StrategyScopeOptionWhenMarkJobNotMatch,
|
StrategyScopeOptionWhenMarkJobNotMatch,
|
||||||
SalaryCalculateWay,
|
SalaryCalculateWay,
|
||||||
@@ -944,6 +1081,7 @@ import mittBus from '../../../utils/mitt'
|
|||||||
import CityChooser from './components/CityChooser.vue'
|
import CityChooser from './components/CityChooser.vue'
|
||||||
import conditions from '@geekgeekrun/geek-auto-start-chat-with-boss/internal-config/job-filter-conditions-20241002.json'
|
import conditions from '@geekgeekrun/geek-auto-start-chat-with-boss/internal-config/job-filter-conditions-20241002.json'
|
||||||
import JobSourceDragOrderer from '../../../features/JobSourceDragOrderer/index.vue'
|
import JobSourceDragOrderer from '../../../features/JobSourceDragOrderer/index.vue'
|
||||||
|
import expectJobFilterTemplateList from './expectJobFilterTemplateList'
|
||||||
|
|
||||||
const gtagRenderer = (name, params?: object) => {
|
const gtagRenderer = (name, params?: object) => {
|
||||||
return baseGtagRenderer(name, {
|
return baseGtagRenderer(name, {
|
||||||
@@ -958,6 +1096,8 @@ const formContent = ref({
|
|||||||
dingtalkRobotAccessToken: '',
|
dingtalkRobotAccessToken: '',
|
||||||
expectCompanies: '',
|
expectCompanies: '',
|
||||||
anyCombineRecommendJobFilter: {},
|
anyCombineRecommendJobFilter: {},
|
||||||
|
combineRecommendJobFilterType: 1,
|
||||||
|
staticCombineRecommendJobFilterConditions: [],
|
||||||
expectJobNameRegExpStr: '',
|
expectJobNameRegExpStr: '',
|
||||||
expectJobTypeRegExpStr: '',
|
expectJobTypeRegExpStr: '',
|
||||||
expectJobDescRegExpStr: '',
|
expectJobDescRegExpStr: '',
|
||||||
@@ -989,7 +1129,10 @@ const formContent = ref({
|
|||||||
type: 'expect',
|
type: 'expect',
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
])
|
]),
|
||||||
|
isSageTimeEnabled: true,
|
||||||
|
sageTimeOpTimes: 100,
|
||||||
|
sageTimePauseMinute: 15
|
||||||
})
|
})
|
||||||
|
|
||||||
const anyCombineBossRecommendFilterHasCondition = computed(() => {
|
const anyCombineBossRecommendFilterHasCondition = computed(() => {
|
||||||
@@ -999,6 +1142,16 @@ const anyCombineBossRecommendFilterHasCondition = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const currentAnyCombineRecommendJobFilterCombinationCount = computed(() => {
|
const currentAnyCombineRecommendJobFilterCombinationCount = computed(() => {
|
||||||
|
if (
|
||||||
|
formContent.value.combineRecommendJobFilterType === CombineRecommendJobFilterType.STATIC_COMBINE
|
||||||
|
) {
|
||||||
|
if (!formContent.value.staticCombineRecommendJobFilterConditions?.length) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return formatStaticCombineFilters(
|
||||||
|
formContent.value.staticCombineRecommendJobFilterConditions
|
||||||
|
)?.filter(Boolean).length
|
||||||
|
}
|
||||||
return calculateTotalCombinations(
|
return calculateTotalCombinations(
|
||||||
formContent.value.anyCombineRecommendJobFilter,
|
formContent.value.anyCombineRecommendJobFilter,
|
||||||
anyCombineBossRecommendFilterHasCondition.value
|
anyCombineBossRecommendFilterHasCondition.value
|
||||||
@@ -1045,6 +1198,12 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
|||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
formContent.value.combineRecommendJobFilterType =
|
||||||
|
res.config['boss.json']?.combineRecommendJobFilterType ?? 1
|
||||||
|
formContent.value.staticCombineRecommendJobFilterConditions = res.config['boss.json']
|
||||||
|
?.staticCombineRecommendJobFilterConditions?.length
|
||||||
|
? res.config['boss.json'].staticCombineRecommendJobFilterConditions
|
||||||
|
: []
|
||||||
if (
|
if (
|
||||||
res.config['boss.json']?.expectJobRegExpStr &&
|
res.config['boss.json']?.expectJobRegExpStr &&
|
||||||
typeof res.config['boss.json']?.expectJobNameRegExpStr === 'undefined' &&
|
typeof res.config['boss.json']?.expectJobNameRegExpStr === 'undefined' &&
|
||||||
@@ -1120,12 +1279,24 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
|||||||
formContent.value.__jobSourceList = formatJobSourceConfigToFormValue(
|
formContent.value.__jobSourceList = formatJobSourceConfigToFormValue(
|
||||||
res.config['boss.json'].jobSourceList || []
|
res.config['boss.json'].jobSourceList || []
|
||||||
)
|
)
|
||||||
|
formContent.value.isSageTimeEnabled = res.config['boss.json'].isSageTimeEnabled ?? true
|
||||||
|
formContent.value.sageTimeOpTimes =
|
||||||
|
isNaN(parseInt(res.config['boss.json'].sageTimeOpTimes)) ||
|
||||||
|
parseInt(res.config['boss.json'].sageTimeOpTimes) < 1
|
||||||
|
? 100
|
||||||
|
: parseInt(res.config['boss.json'].sageTimeOpTimes)
|
||||||
|
formContent.value.sageTimePauseMinute =
|
||||||
|
isNaN(parseFloat(res.config['boss.json'].sageTimePauseMinute)) ||
|
||||||
|
parseFloat(res.config['boss.json'].sageTimePauseMinute) < 0
|
||||||
|
? 15
|
||||||
|
: parseFloat(res.config['boss.json'].sageTimePauseMinute)
|
||||||
})
|
})
|
||||||
|
|
||||||
const jobSourceFormItemSectionEl = ref()
|
const jobSourceFormItemSectionEl = ref()
|
||||||
const jobDetailRegExpSectionEl = ref()
|
const jobDetailRegExpSectionEl = ref()
|
||||||
const formRules = {
|
const formRules = {
|
||||||
expectJobNameRegExpStr: {
|
expectJobNameRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
validator(_, value, cb) {
|
validator(_, value, cb) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
cb()
|
cb()
|
||||||
@@ -1134,16 +1305,17 @@ const formRules = {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
new RegExp(value, 'ig')
|
new RegExp(value, 'ig')
|
||||||
gtagRenderer('valid_reg_exp_for_expect_job_name')
|
gtagRenderer('valid_reg_exp_for_expect_job_name', { v: value })
|
||||||
cb()
|
cb()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb(new Error(`正则无效:${err?.message}`))
|
cb(new Error(`正则无效:${err?.message}`))
|
||||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||||
gtagRenderer('invalid_reg_exp_for_expect_job_name')
|
gtagRenderer('invalid_reg_exp_for_expect_job_name', { v: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expectJobTypeRegExpStr: {
|
expectJobTypeRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
validator(_, value, cb) {
|
validator(_, value, cb) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
cb()
|
cb()
|
||||||
@@ -1152,16 +1324,17 @@ const formRules = {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
new RegExp(value, 'ig')
|
new RegExp(value, 'ig')
|
||||||
gtagRenderer('valid_reg_exp_for_expect_job_type')
|
gtagRenderer('valid_reg_exp_for_expect_job_type', { v: value })
|
||||||
cb()
|
cb()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb(new Error(`正则无效:${err?.message}`))
|
cb(new Error(`正则无效:${err?.message}`))
|
||||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||||
gtagRenderer('invalid_reg_exp_for_expect_job_type')
|
gtagRenderer('invalid_reg_exp_for_expect_job_type', { v: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expectJobDescRegExpStr: {
|
expectJobDescRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
validator(_, value, cb) {
|
validator(_, value, cb) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
cb()
|
cb()
|
||||||
@@ -1170,12 +1343,12 @@ const formRules = {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
new RegExp(value, 'ig')
|
new RegExp(value, 'ig')
|
||||||
gtagRenderer('valid_reg_exp_for_expect_job_desc')
|
gtagRenderer('valid_reg_exp_for_expect_job_desc', { v: value })
|
||||||
cb()
|
cb()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb(new Error(`正则无效:${err?.message}`))
|
cb(new Error(`正则无效:${err?.message}`))
|
||||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||||
gtagRenderer('invalid_reg_exp_for_expect_job_desc')
|
gtagRenderer('invalid_reg_exp_for_expect_job_desc', { v: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1209,14 +1382,51 @@ const formRules = {
|
|||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
sageTimeOpTimes: {
|
||||||
|
validator(_, value, cb) {
|
||||||
|
if (!formContent.value.isSageTimeEnabled) {
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (value < 1 || isNaN(parseInt(value))) {
|
||||||
|
cb(new Error(`最小值为1,请重试`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sageTimePauseMinute: {
|
||||||
|
validator(_, value, cb) {
|
||||||
|
if (!formContent.value.isSageTimeEnabled) {
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (value < 0 || isNaN(parseFloat(value))) {
|
||||||
|
cb(new Error(`最小值为0,请重试`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
gtagRenderer('save_config_and_launch_clicked', {
|
gtagRenderer('save_config_and_launch_clicked', {
|
||||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken
|
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken,
|
||||||
|
expect_job_name_reg_exp_str: formContent.value?.expectJobNameRegExpStr,
|
||||||
|
expect_job_type_reg_exp_str: formContent.value?.expectJobTypeRegExpStr,
|
||||||
|
expect_job_desc_reg_exp_str: formContent.value?.expectJobDescRegExpStr,
|
||||||
|
crjf_type: formContent.value?.combineRecommendJobFilterType,
|
||||||
|
crjf_cc: currentAnyCombineRecommendJobFilterCombinationCount.value?.toLocaleString?.(),
|
||||||
|
sage_t_config: JSON.stringify({
|
||||||
|
isEnabled: formContent.value.isSageTimeEnabled,
|
||||||
|
pauseMinute: formContent.value.sageTimePauseMinute,
|
||||||
|
opTimes: formContent.value.sageTimeOpTimes
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
// remove the obsolete filter - expectJobRegExpStr
|
||||||
formContent.value.expectJobRegExpStr = undefined
|
formContent.value.expectJobRegExpStr = undefined
|
||||||
try {
|
try {
|
||||||
await formRef.value!.validate()
|
await formRef.value!.validate()
|
||||||
@@ -1245,7 +1455,17 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
gtagRenderer('save_config_clicked', {
|
gtagRenderer('save_config_clicked', {
|
||||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken
|
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken,
|
||||||
|
expect_job_name_reg_exp_str: formContent.value?.expectJobNameRegExpStr,
|
||||||
|
expect_job_type_reg_exp_str: formContent.value?.expectJobTypeRegExpStr,
|
||||||
|
expect_job_desc_reg_exp_str: formContent.value?.expectJobDescRegExpStr,
|
||||||
|
crjf_type: formContent.value?.combineRecommendJobFilterType,
|
||||||
|
crjf_cc: currentAnyCombineRecommendJobFilterCombinationCount.value?.toLocaleString?.(),
|
||||||
|
sage_t_config: JSON.stringify({
|
||||||
|
isEnabled: formContent.value.isSageTimeEnabled,
|
||||||
|
pauseMinute: formContent.value.sageTimePauseMinute,
|
||||||
|
opTimes: formContent.value.sageTimeOpTimes
|
||||||
|
})
|
||||||
})
|
})
|
||||||
normalizeExpectCompanies()
|
normalizeExpectCompanies()
|
||||||
try {
|
try {
|
||||||
@@ -1322,53 +1542,6 @@ function handleExpectCompanyTemplateClicked(item) {
|
|||||||
formContent.value.expectCompanies = item.value
|
formContent.value.expectCompanies = item.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectJobFilterTemplateList = [
|
|
||||||
{
|
|
||||||
name: '不限职位(随便投)',
|
|
||||||
config: {
|
|
||||||
expectJobNameRegExpStr: '',
|
|
||||||
expectJobTypeRegExpStr: '',
|
|
||||||
expectJobDescRegExpStr: '',
|
|
||||||
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '研发 - 前端开发工程师',
|
|
||||||
config: {
|
|
||||||
expectJobNameRegExpStr: '前端|H5|FE',
|
|
||||||
expectJobTypeRegExpStr: '前端开发|javascript',
|
|
||||||
expectJobDescRegExpStr: '前端|vue|react|node|js|javascript|H5',
|
|
||||||
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '研发 - Java',
|
|
||||||
config: {
|
|
||||||
expectJobNameRegExpStr: '\\bJava\\b',
|
|
||||||
expectJobTypeRegExpStr: '\\bJava\\b',
|
|
||||||
expectJobDescRegExpStr: '\\bJava\\b|JVM|消息队列|MQ|MySQL|Nginx|Redis|Dubbo',
|
|
||||||
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '人力 - 员工关系',
|
|
||||||
config: {
|
|
||||||
expectJobNameRegExpStr: '员工关系|劳动关系|SSC|人力资源|人资',
|
|
||||||
expectJobTypeRegExpStr: '员工关系|人力资源',
|
|
||||||
expectJobDescRegExpStr: '社保|考勤|入职|离职',
|
|
||||||
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '人力 - 招聘',
|
|
||||||
config: {
|
|
||||||
expectJobNameRegExpStr: '招聘|招聘HR|招聘专员|招聘顾问|招聘专家|Recruiter|人力资源|人资',
|
|
||||||
expectJobTypeRegExpStr: '招聘|人力资源|猎头顾问',
|
|
||||||
expectJobDescRegExpStr: '简历筛选|面试安排|offer|猎头',
|
|
||||||
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.SOME
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
function handleExpectJobFilterTemplateClicked(item) {
|
function handleExpectJobFilterTemplateClicked(item) {
|
||||||
gtagRenderer('expect_job_filter_tpl_clicked', {
|
gtagRenderer('expect_job_filter_tpl_clicked', {
|
||||||
name: item.name
|
name: item.name
|
||||||
@@ -1619,6 +1792,17 @@ function formatJobSourceFormValueToConfig(formValue = []) {
|
|||||||
return it
|
return it
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const combineRecommendJobFilterTypeOptions = [
|
||||||
|
{
|
||||||
|
name: '使用自由组合条件进行遍历',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '使用固定组合条件进行遍历',
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h100vh">
|
<div class="flex h100vh">
|
||||||
<div class="flex flex-col w200px pt30px pl30px aside-nav of-hidden">
|
<div class="flex flex-col min-w200px w200px pt30px pl30px aside-nav of-hidden">
|
||||||
<div class="nav-list flex-1 of-auto">
|
<div class="nav-list flex-1 of-auto">
|
||||||
<RouterLink to="./GeekAutoStartChatWithBoss">
|
<RouterLink to="./GeekAutoStartChatWithBoss">
|
||||||
Boss炸弹
|
Boss炸弹
|
||||||
|
|||||||
Reference in New Issue
Block a user