mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-07-13 00:11:43 +08:00
Compare commits
27 Commits
ui-v0.9.0
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5225006cc | ||
|
|
fdf2c61620 | ||
|
|
1f140121fb | ||
|
|
57cf631b86 | ||
|
|
36aef1c16d | ||
|
|
d21972020e | ||
|
|
51ca5dd71d | ||
|
|
e7d5ef382e | ||
|
|
59a36dc7bd | ||
|
|
73b57e21e4 | ||
|
|
6f0d98653c | ||
|
|
3bf6d4c358 | ||
|
|
2c7ecafee2 | ||
|
|
f88a5a2889 | ||
|
|
ac19ae3832 | ||
|
|
a25b047f99 | ||
|
|
42fc66a0f4 | ||
|
|
203fbba9a0 | ||
|
|
b821bc500c | ||
|
|
0032a356e5 | ||
|
|
b9c4976291 | ||
|
|
558891ad9f | ||
|
|
d2da9f5a7d | ||
|
|
71068be21f | ||
|
|
2b0cf2a2fa | ||
|
|
ceaf68e4eb | ||
|
|
10cc248784 |
40
README.md
40
README.md
@@ -12,33 +12,34 @@
|
||||
## 程序有哪些功能?运行逻辑是什么?怎样使用?
|
||||
|
||||
### Boss炸弹
|
||||
扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的Boss。
|
||||
扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的Boss。可以帮你开聊推荐列表中的职位、根据设置的求职期望推荐的职位以及根据你设置的关键词通过搜索找到的职位;可以通过拖拽的方式自定义搜索顺序哦~
|
||||
|
||||
<img width="1024" alt="image" src="https://github.com/user-attachments/assets/ebf5c51c-f52c-4426-a60a-97916f663c6a" />
|
||||
<img width="1280" height="2474" alt="Snipaste_2025-07-15_13-05-35" src="https://github.com/user-attachments/assets/30308ec2-1e3b-4a8f-b3fe-7d205394578d" />
|
||||
|
||||
|
||||
#### 匹配步骤
|
||||
1. 按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
|
||||
2. 检查Boss活跃度
|
||||
- 如果Boss活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在Boss上看到这个职位,且将会推荐新职位置换这个职位
|
||||
3. 对职位名称、职位类型、职位描述进行匹配
|
||||
- 如果匹配则自动点击开聊按钮
|
||||
- 不匹配则标记这个职位为不合适,一段时间内你将不会在Boss上看到这个职位,且将会推荐新职位置换这个职位
|
||||
1. 选择职位来源、职位筛选条件
|
||||
<img width="1680" height="1050" alt="image" src="https://github.com/user-attachments/assets/cbdea071-ec28-49b0-af12-1018aa9579a9" />
|
||||
|
||||
2. 按照公司名称在列表中查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
|
||||
3. 对工作地、薪资、工作经验、职位名称、职位类型、职位描述、Boss活跃度进行匹配
|
||||
- 如果可以匹配,则自动点击开聊按钮
|
||||
- 不匹配则根据你设置的标记不合适的策略将职位标记为不合适
|
||||
- 首先查看是否命中策略为“在Boss直聘上标记不合适”的配置,如果命中则在Boss直聘上点击相关UI,标记不合适,一段时间内你将不会在Boss上看到这个职位,且将会推荐新职位置换这个职位(对于“推荐列表中的职位”、“通过搜索找到的职位”,此项会回落到“在本地数据库中标记不合适,且7天内再遇到这个职位时直接跳过”)
|
||||
- 然后查看是否命中策略为“在本地数据库中标记不合适,且7天内再遇到这个职位时直接跳过”的配置,如果命中则仅在本地数据库中记录,且本次运行将无视这个职位
|
||||
- 最后查看是否命中策略为了“仅在本次运行中记录不合适,且本次运行再遇到这个职位时直接跳过”的配置,如果命中则本次运行将无视这个职位,但下次运行可能还会查看这个职位
|
||||
|
||||
#### 异常情况
|
||||
- 当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
|
||||
- 当前页面筛选条件下,如果没有更多可投递职位,则自动切换筛选条件,以获取更多新职位;如果所有筛选条件搜索完没有更多可投递职位,则切换其它职位来源继续查找更多新职位;如果所有职位来源筛选完没有更多可投递职位,则等候一段时间后,会回到第一个职位来源的第一个筛选条件,从头开始查找职位
|
||||
- 如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
|
||||
|
||||
#### 运行效果
|
||||
|
||||
> Boss直聘界面已改版,如下是旧版Boss直聘界面
|
||||
|
||||
https://github.com/user-attachments/assets/672f1637-dd54-416c-922e-1ecf09b0874a
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 已读不回提醒器
|
||||
Boss不明原因已读不回?简历就是投不出去?
|
||||
|
||||
@@ -118,12 +119,12 @@ Boss不明原因已读不回?简历就是投不出去?
|
||||
```
|
||||
1. 双击应用程序图标以启动程序;启动后,将引导你为初次使用进行一些配置
|
||||
|
||||
## 关于软件不能正常启动、不能按预期运行的一些说明(朋友们帮忙测试一下吧)
|
||||
## 关于软件不能正常启动、不能按预期运行的一些说明
|
||||
本程序一直都在不断地更新迭代中,包括新功能的增加以及Bug的修复。
|
||||
|
||||
然而作者本人只有 1台 macOS (Apple Silicon)设备、1台 Windows 设备,且只有作者1人自测(白天工作,晚上熬夜开发、测试),测试条件不是太好(人力不足、设备不足),测试时只要确保两台设备能够正常运行本程序主要流程以及新增逻辑时,就会准出、发版。
|
||||
然而作者本人只有 1台 macOS (Apple Silicon)设备、1台 Windows 设备,且只有作者1人自测(白天工作,晚上熬夜开发、测试),测试条件不是太好(人力不足、设备不足),测试时只要确保两台设备能够正常运行本程序主要流程以及新增逻辑时,即认为可以准出、发版。
|
||||
|
||||
这将不能确保任何设备一定能够正确运行本程序;因此期望朋友们使用过程中,可以帮忙**参与测试**并通过Issue区**反馈问题**。Issue区任何内容我都会看,问题也会尝试进行修复。
|
||||
所以不能确保任何设备一定能够正确运行本程序;因此期望朋友们使用过程中,可以帮忙**参与测试**并通过Issue区**反馈问题**。Issue区任何内容我都会看,问题也会尝试进行修复。
|
||||
|
||||
如果部分朋友超级喜欢写代码,或者本身工作就是写代码,可以帮忙下载 [Debugtron](https://github.com/pd4d10/debugtron/releases/tag/v1.0.0-alpha.0) 来调试本程序,查看反馈本程序的日志(如下图)。
|
||||
|
||||
@@ -185,6 +186,13 @@ Boss不明原因已读不回?简历就是投不出去?
|
||||
- 请在Boss直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在Boss直聘上活跃,请**在Boss直聘上屏蔽当前公司及与之关联的公司**。
|
||||
- 本程序经历过了多次测试,理论上来说大部分情况下可以正常运行,但可能也会出现测试用例覆盖不到位,导致程序不按预期运行的情况;如果您有顾虑,建议通过VMware Workstation/Fusion、Oracle VirtualBox、Microsoft Hyper-V等虚拟化技术运行本程序。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
|
||||
|
||||
## 想联系作者?想反馈使用体验上的问题?想找求职搭子?
|
||||
|
||||
<img width="360" alt="QQ Group QRCode" src="https://github.com/user-attachments/assets/67560948-467b-44f5-a3e3-c8713255d8b7" />
|
||||
|
||||
|
||||
欢迎进群聊天(刚建的群,人有点少,大胆聊就好)
|
||||
|
||||
--------
|
||||
|
||||
以上
|
||||
|
||||
@@ -124,3 +124,35 @@ export function checkAnyCombineBossRecommendFilterHasCondition(value) {
|
||||
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] : []
|
||||
}
|
||||
})
|
||||
result.unshift(null)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"combineRecommendJobFilterType": 1,
|
||||
"anyCombineRecommendJobFilter": {
|
||||
"salaryList": [],
|
||||
"experienceList": [],
|
||||
@@ -6,6 +7,7 @@
|
||||
"scaleList": [],
|
||||
"industryList": []
|
||||
},
|
||||
"staticCombineRecommendJobFilterConditions": [],
|
||||
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
|
||||
"expectJobRegExpStr": "",
|
||||
"jobNotMatchStrategy": 1,
|
||||
|
||||
@@ -15,12 +15,21 @@ import { readConfigFile, writeStorageFile, ensureConfigFileExist, readStorageFil
|
||||
import {
|
||||
calculateTotalCombinations,
|
||||
combineFiltersWithConstraintsGenerator,
|
||||
checkAnyCombineBossRecommendFilterHasCondition
|
||||
checkAnyCombineBossRecommendFilterHasCondition,
|
||||
formatStaticCombineFilters,
|
||||
} from './combineCalculator.mjs'
|
||||
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 { 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 {
|
||||
activeDescList,
|
||||
RECOMMEND_JOB_ENTRY_SELECTOR,
|
||||
@@ -85,8 +94,10 @@ const bossCookies = readStorageFile('boss-cookies.json')
|
||||
const bossLocalStorage = readStorageFile('boss-local-storage.json')
|
||||
|
||||
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 staticCombineRecommendJobFilterConditions = readConfigFile('boss.json').staticCombineRecommendJobFilterConditions ?? []
|
||||
let isSkipEmptyConditionForCombineRecommendJobFilter = readConfigFile('boss.json').isSkipEmptyConditionForCombineRecommendJobFilter
|
||||
if (!checkAnyCombineBossRecommendFilterHasCondition(anyCombineRecommendJobFilter)) {
|
||||
isSkipEmptyConditionForCombineRecommendJobFilter = false
|
||||
@@ -108,7 +119,18 @@ const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
|
||||
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
// 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 strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
@@ -251,33 +273,43 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
||||
await writeStorageFile('job-not-suit-reason-code-to-text-cache.json', reasonCodeToTextMap)
|
||||
await sleepWithRandomDelay(2000)
|
||||
const chooseReasonDialogProxy = await(async() => {
|
||||
const alls = await page.$$('.zp-dialog-wrap.zp-feedback-dialog.v-transfer-dom')
|
||||
const alls = await page.$$('.zp-dialog-wrap.zp-feedback-dialog')
|
||||
return alls?.[alls.length - 1]
|
||||
})()
|
||||
let isOptionChosen = false
|
||||
if (chooseReasonDialogProxy) {
|
||||
switch (reasonCode) {
|
||||
case MarkAsNotSuitReason.BOSS_INACTIVE: {
|
||||
const bossNotActiveOptionProxy = await chooseReasonDialogProxy.$(`.zp-type-item[title="BOSS活跃度低"]`)
|
||||
if (bossNotActiveOptionProxy) {
|
||||
await bossNotActiveOptionProxy.click()
|
||||
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title="BOSS活跃度低"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="职位停招/招满"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
||||
if (opProxy) {
|
||||
await opProxy.click()
|
||||
isOptionChosen = true
|
||||
} else {
|
||||
const recruitStoppedOptionProxy = await chooseReasonDialogProxy.$(`.zp-type-item[title="职位停招/招满"]`)
|
||||
if (recruitStoppedOptionProxy) {
|
||||
await recruitStoppedOptionProxy.click()
|
||||
isOptionChosen = true
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT:
|
||||
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT:
|
||||
case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: {
|
||||
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: {
|
||||
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="重复推荐"]`))
|
||||
if (opProxy) {
|
||||
await opProxy.click()
|
||||
isOptionChosen = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: {
|
||||
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="重复推荐"]`))
|
||||
if (opProxy) {
|
||||
await opProxy.click()
|
||||
isOptionChosen = true
|
||||
@@ -286,9 +318,11 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_NOT_SUIT:
|
||||
default: {
|
||||
const jobNotSuitOptionProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="职位"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||
if (jobNotSuitOptionProxy) {
|
||||
await jobNotSuitOptionProxy.click()
|
||||
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="职位"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`))
|
||||
if (opProxy) {
|
||||
await opProxy.click()
|
||||
isOptionChosen = true
|
||||
}
|
||||
break
|
||||
@@ -336,7 +370,7 @@ export function testIfJobTitleOrDescriptionSuit (jobInfo, matchLogic) {
|
||||
try {
|
||||
if (expectJobNameRegExpStr.trim()) {
|
||||
const regExp = new RegExp(expectJobNameRegExpStr, 'i')
|
||||
isJobNameSuit = regExp.test(jobInfo.jobName)
|
||||
isJobNameSuit = regExp.test(jobInfo.jobName?.replace(/\n/g, '') ?? '')
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
@@ -344,7 +378,7 @@ export function testIfJobTitleOrDescriptionSuit (jobInfo, matchLogic) {
|
||||
try {
|
||||
if (expectJobTypeRegExpStr.trim()) {
|
||||
const regExp = new RegExp(expectJobTypeRegExpStr, 'i')
|
||||
isJobTypeSuit = regExp.test(jobInfo.positionName)
|
||||
isJobTypeSuit = regExp.test(jobInfo.positionName?.replace(/\n/g, '') ?? '')
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
@@ -352,7 +386,7 @@ export function testIfJobTitleOrDescriptionSuit (jobInfo, matchLogic) {
|
||||
try {
|
||||
if (expectJobDescRegExpStr.trim()) {
|
||||
const regExp = new RegExp(expectJobDescRegExpStr, 'i')
|
||||
isJobDescSuit = regExp.test(jobInfo.postDescription)
|
||||
isJobDescSuit = regExp.test(jobInfo.postDescription?.replace(/\n/g, '') ?? '')
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
@@ -511,6 +545,11 @@ async function toRecommendPage (hooks) {
|
||||
) {
|
||||
throw new Error("ACCESS_IS_DENIED")
|
||||
}
|
||||
|
||||
await page.waitForFunction(({ recommendJobPageUrl }) => {
|
||||
return location.href.startsWith(recommendJobPageUrl) && document.readyState === 'complete'
|
||||
}, undefined, { recommendJobPageUrl })
|
||||
|
||||
hooks.pageLoaded?.call()
|
||||
|
||||
let userInfoResponse = await userInfoPromise
|
||||
@@ -525,20 +564,6 @@ async function toRecommendPage (hooks) {
|
||||
await storeStorage(page).catch(() => void 0)
|
||||
}
|
||||
|
||||
// check set security question tip modal
|
||||
let setSecurityQuestionTipModelProxy = await page.$('.dialog-wrap.dialog-account-safe')
|
||||
if (
|
||||
setSecurityQuestionTipModelProxy
|
||||
) {
|
||||
await sleep(1000)
|
||||
setSecurityQuestionTipModelProxy = await page.$('.dialog-wrap.dialog-account-safe')
|
||||
const closeButtonProxy = await setSecurityQuestionTipModelProxy?.$('.close')
|
||||
|
||||
if (setSecurityQuestionTipModelProxy && closeButtonProxy) {
|
||||
await closeButtonProxy.click()
|
||||
}
|
||||
}
|
||||
|
||||
const computedSourceList = []
|
||||
for (const source of normalizedJobSource) {
|
||||
switch (source.type) {
|
||||
@@ -628,11 +653,33 @@ async function toRecommendPage (hooks) {
|
||||
|
||||
let currentSourceIndex = 0
|
||||
afterPageLoad: while (true) {
|
||||
// check set security question tip modal
|
||||
let setSecurityQuestionTipModelProxy
|
||||
try {
|
||||
setSecurityQuestionTipModelProxy = await page.waitForSelector('.dialog-wrap.dialog-account-safe', { timeout: 3 * 1000 })
|
||||
}
|
||||
catch(err) {
|
||||
console.log(`cannot find set security question tip modal, just continue`)
|
||||
}
|
||||
if (
|
||||
setSecurityQuestionTipModelProxy
|
||||
) {
|
||||
await sleep(1000)
|
||||
setSecurityQuestionTipModelProxy = await page.$('.dialog-wrap.dialog-account-safe')
|
||||
const closeButtonProxy = await setSecurityQuestionTipModelProxy?.$('.close')
|
||||
|
||||
if (setSecurityQuestionTipModelProxy && closeButtonProxy) {
|
||||
await closeButtonProxy.click()
|
||||
}
|
||||
}
|
||||
|
||||
const filterConditions =
|
||||
combineRecommendJobFilterType === CombineRecommendJobFilterType.STATIC_COMBINE
|
||||
? formatStaticCombineFilters(staticCombineRecommendJobFilterConditions)
|
||||
: combineFiltersWithConstraintsGenerator(anyCombineRecommendJobFilter)
|
||||
let expectJobList
|
||||
iterateFilterCondition: for (
|
||||
const filterCondition of combineFiltersWithConstraintsGenerator(
|
||||
anyCombineRecommendJobFilter
|
||||
)
|
||||
const filterCondition of filterConditions
|
||||
) {
|
||||
findInCurrentFilterCondition: while(true) {
|
||||
await sleepWithRandomDelay(2500)
|
||||
@@ -657,11 +704,17 @@ async function toRecommendPage (hooks) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isSkipEmptyConditionForCombineRecommendJobFilter &&
|
||||
Object.keys(filterCondition).length &&
|
||||
Object.keys(filterCondition).every(k => !filterCondition[k]?.length)
|
||||
(
|
||||
combineRecommendJobFilterType === CombineRecommendJobFilterType.STATIC_COMBINE && filterCondition === null
|
||||
)
|
||||
||
|
||||
(
|
||||
combineRecommendJobFilterType === CombineRecommendJobFilterType.ANY_COMBINE &&
|
||||
isSkipEmptyConditionForCombineRecommendJobFilter &&
|
||||
Object.keys(filterCondition).length &&
|
||||
Object.keys(filterCondition).every(k => !filterCondition[k]?.length)
|
||||
)
|
||||
) {
|
||||
sleep(4000)
|
||||
continue iterateFilterCondition
|
||||
@@ -784,7 +837,7 @@ async function toRecommendPage (hooks) {
|
||||
if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
|
||||
return false
|
||||
}
|
||||
if (expectSalaryLow && salaryData.low < expectSalaryLow) {
|
||||
if (expectSalaryLow && salaryData.high < expectSalaryLow) {
|
||||
return false
|
||||
}
|
||||
} else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||
@@ -792,7 +845,7 @@ async function toRecommendPage (hooks) {
|
||||
if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
|
||||
return false
|
||||
}
|
||||
if (expectSalaryLow && (salaryData.low * salaryDataMonth) / 10 < expectSalaryLow) {
|
||||
if (expectSalaryLow && (salaryData.high * salaryDataMonth) / 10 < expectSalaryLow) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -962,7 +1015,8 @@ async function toRecommendPage (hooks) {
|
||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
} catch(err) {
|
||||
console.log(`mark boss inactive failed`, err)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -998,7 +1052,8 @@ async function toRecommendPage (hooks) {
|
||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
} catch(err) {
|
||||
console.log(`mark job city not suit failed`, err)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1034,7 +1089,8 @@ async function toRecommendPage (hooks) {
|
||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
} catch(err) {
|
||||
console.log(`mark job work exp not suit failed`, err)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1071,7 +1127,8 @@ async function toRecommendPage (hooks) {
|
||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
} catch(err) {
|
||||
console.log(`mark job detail not suit failed`, err)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1110,7 +1167,8 @@ async function toRecommendPage (hooks) {
|
||||
jobSource: JobSource[computedSourceList[currentSourceIndex]?.type]
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
} catch(err) {
|
||||
console.log(`mark job salary not suit failed`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1165,6 +1223,12 @@ async function toRecommendPage (hooks) {
|
||||
await notSuitConditionHandleMap[markOnLocalDbCondition]()
|
||||
continue continueFind
|
||||
}
|
||||
// 3.
|
||||
const noOpCondition = Object.keys(notSuitReasonIdToStrategyMap).find(k => notSuitReasonIdToStrategyMap[k] === MarkAsNotSuitOp.NO_OP)
|
||||
if (noOpCondition) {
|
||||
await notSuitConditionHandleMap[noOpCondition]()
|
||||
continue continueFind
|
||||
}
|
||||
// #endregion
|
||||
if (
|
||||
// test company again - when allow list not include target company, just skip
|
||||
@@ -1227,16 +1291,7 @@ async function toRecommendPage (hooks) {
|
||||
);
|
||||
const res = await addFriendResponse.json()
|
||||
|
||||
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 {
|
||||
if (res.code === 0) {
|
||||
await hooks.newChatStartup?.promise(
|
||||
targetJobData,
|
||||
{
|
||||
@@ -1252,6 +1307,28 @@ async function toRecommendPage (hooks) {
|
||||
await closeDialogButtonProxy.click()
|
||||
await sleepWithRandomDelay(2000)
|
||||
}
|
||||
// TODO:
|
||||
// else if (res.zpData.bizCode === 1 && res.zpData.bizData?.chatRemindDialog?.blockLevel === 0 && /还剩\d+次沟通机会/.test(res.zpData.bizData?.chatRemindDialog?.content)) {
|
||||
// debugger
|
||||
// }
|
||||
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')
|
||||
}
|
||||
// #endregion
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
|
||||
@@ -34,3 +34,8 @@ export enum JobSource {
|
||||
recommend = 2,
|
||||
search = 3,
|
||||
}
|
||||
|
||||
export enum CombineRecommendJobFilterType {
|
||||
ANY_COMBINE = 1,
|
||||
STATIC_COMBINE = 2,
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import "reflect-metadata";
|
||||
import { type DataSource } from "typeorm";
|
||||
import { requireTypeorm } from "./utils/module-loader";
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { BossInfo } from "./entity/BossInfo";
|
||||
import { BossInfoChangeLog } from "./entity/BossInfoChangeLog";
|
||||
@@ -38,6 +37,7 @@ import { MarkAsNotSuitOp, MarkAsNotSuitReason } from "./enums";
|
||||
import { AddColumnForMarkAsNotSuitLog1746092370665 } from "./migrations/1746092370665-AddColumnForMarkAsNotSuitLog";
|
||||
import { Init1000000000000 } from "./migrations/1000000000000-Init";
|
||||
import { AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog1752380078526 } from "./migrations/1752380078526-AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog";
|
||||
const lodashImportPromise = import('lodash-es')
|
||||
|
||||
export function initDb(dbFilePath) {
|
||||
const { DataSource } = requireTypeorm()
|
||||
@@ -175,7 +175,16 @@ export default class SqlitePlugin {
|
||||
}
|
||||
const last30DayChatStartupRecords = (await getChatStartupRecordsInLastSomeDays(ds, 30)) ?? [];
|
||||
const chattedJobIds = last30DayChatStartupRecords.map(it => it.encryptJobId)
|
||||
const chattedBossIds = ((await getBossIdsByJobIds(ds, chattedJobIds)) ?? []).map(it => it.encryptBossId)
|
||||
if (chattedJobIds.length === 0) {
|
||||
return
|
||||
}
|
||||
const { chunk } = await lodashImportPromise
|
||||
const chattedJobIdChunks = chunk(chattedJobIds, 200)
|
||||
const chattedBossIds = [];
|
||||
for (const chattedJobIdChunk of chattedJobIdChunks) {
|
||||
const chattedBossIdChunk = ((await getBossIdsByJobIds(ds, chattedJobIdChunk)) ?? []).map(it => it.encryptBossId)
|
||||
chattedBossIds.push(...chattedBossIdChunk)
|
||||
}
|
||||
for (const id of chattedBossIds) {
|
||||
blockBossNotNewChat.add(id)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineConfig, externalizeDepsPlugin, loadEnv } from 'electron-vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
import { presetUno, presetAttributify, presetIcons } from 'unocss'
|
||||
import transformerDirective from "@unocss/transformer-directives";
|
||||
import transformerDirective from '@unocss/transformer-directives'
|
||||
import Replace from 'unplugin-replace/vite'
|
||||
|
||||
process.env = { ...process.env, ...loadEnv(process.env.NODE_ENV!, process.cwd()) }
|
||||
@@ -13,11 +13,17 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
external: []
|
||||
},
|
||||
minify: 'terser'
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||
},
|
||||
plugins: [
|
||||
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({
|
||||
delimiters: ['', ''],
|
||||
@@ -26,11 +32,11 @@ export default defineConfig({
|
||||
values: [
|
||||
{
|
||||
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,
|
||||
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: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
build: {
|
||||
minify: 'terser'
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
@@ -52,11 +58,11 @@ export default defineConfig({
|
||||
vue(),
|
||||
UnoCSS({
|
||||
presets: [presetUno(), presetAttributify(), presetIcons()],
|
||||
transformers: [transformerDirective()],
|
||||
transformers: [transformerDirective()]
|
||||
})
|
||||
],
|
||||
build: {
|
||||
minify: 'terser'
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "geekgeekrun-ui",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.4",
|
||||
"description": "Boss 炸弹 - 自动开聊Boss,助力每位打工人求职!",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "geekgeekrun",
|
||||
@@ -38,6 +38,7 @@
|
||||
"JSONStream": "^1.3.5",
|
||||
"diff": "^7.0.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimist": "^1.2.8",
|
||||
"mitt": "^3.0.1",
|
||||
"node-machine-id": "^1.1.12",
|
||||
@@ -72,7 +73,6 @@
|
||||
"eslint-plugin-vue": "^9.20.1",
|
||||
"find-chrome-bin": "^2.0.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"normalize.css": "^8.0.1",
|
||||
"prettier": "^3.2.4",
|
||||
"sass": "^1.70.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "0.9.0",
|
||||
"buildVersion": 16,
|
||||
"buildTime": 1752412320912,
|
||||
"buildHash": "d3e086f32184dd2a36819c0f9697228fa1316ada",
|
||||
"version": "0.9.4",
|
||||
"buildVersion": 20,
|
||||
"buildTime": 1760377927509,
|
||||
"buildHash": "51ca5dd71d62de2965fb53cff9d03c0d3552eabb",
|
||||
"name": "geekgeekrun-ui"
|
||||
}
|
||||
@@ -159,6 +159,13 @@ export default function initIpc() {
|
||||
if (hasOwn(payload, 'jobSourceList')) {
|
||||
bossConfig.jobSourceList = payload.jobSourceList
|
||||
}
|
||||
if (hasOwn(payload, 'combineRecommendJobFilterType')) {
|
||||
bossConfig.combineRecommendJobFilterType = payload.combineRecommendJobFilterType
|
||||
}
|
||||
if (hasOwn(payload, 'staticCombineRecommendJobFilterConditions')) {
|
||||
bossConfig.staticCombineRecommendJobFilterConditions =
|
||||
payload.staticCombineRecommendJobFilterConditions
|
||||
}
|
||||
|
||||
promiseArr.push(writeConfigFile('boss.json', bossConfig))
|
||||
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
<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.scaleList"
|
||||
: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
|
||||
}
|
||||
})
|
||||
|
||||
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>
|
||||
@@ -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
|
||||
@@ -22,7 +22,7 @@
|
||||
<div w-full>
|
||||
<div ref="jobSourceFormItemSectionEl" font-size-16px>
|
||||
<div>
|
||||
职位来源及其查找顺序
|
||||
你想投递Boss直聘上哪些列表里的职位?
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom-start"
|
||||
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div font-size-12px>
|
||||
拖放条目前方的手柄以调整职位来源查找顺序;点击条目前方的开关以启用/禁用对应的职位来源
|
||||
拖放条目前方的手柄以调整职位列表查找顺序;点击条目前方的开关以启用/禁用对应的职位列表
|
||||
</div>
|
||||
<JobSourceDragOrderer
|
||||
v-model="formContent.__jobSourceList"
|
||||
@@ -49,62 +49,99 @@
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="config-section">
|
||||
<el-form-item prop="filter">
|
||||
<div font-size-16px>
|
||||
职位筛选条件
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom-start"
|
||||
@show="gtagRenderer('tooltip_show_about_wrongly_mark_not_suit')"
|
||||
>
|
||||
<template #content>
|
||||
<ul m0 line-height-1.5em w-540px pl2em>
|
||||
<li>
|
||||
这个配置决定了下面列表中会展示哪些职位。查找职位时,会在Boss直聘页面上自动组合下方设置的筛选条件。对应UI:<br />
|
||||
<img h-270px src="../resources/intro-of-job-filter-auto-change.png" />
|
||||
</li>
|
||||
<li color-orange>
|
||||
建议不要筛选过多条件,否则最终组合数会成为一个很大的数字。这将在当前职位中尝试太多筛选条件,不能及时进入下一个职位,且会增加命中风控的概率
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<el-button type="text" font-size-12px
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span
|
||||
>这个配置是如何工作的?</el-button
|
||||
<el-form-item prop="filter" mt18px mb0px w-full>
|
||||
<div flex-1>
|
||||
<div font-size-16px>
|
||||
你希望Boss直聘为你筛选出什么样的职位?
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom-start"
|
||||
@show="gtagRenderer('tooltip_show_about_wrongly_mark_not_suit')"
|
||||
>
|
||||
</el-tooltip>
|
||||
<template #content>
|
||||
<ul m0 line-height-1.5em w-540px pl2em>
|
||||
<li>
|
||||
这个配置决定了下面列表中会展示哪些职位。查找职位时,会在Boss直聘页面上自动组合下方设置的筛选条件。对应UI:<br />
|
||||
<img h-270px src="../resources/intro-of-job-filter-auto-change.png" />
|
||||
</li>
|
||||
<li color-orange>
|
||||
建议不要筛选过多条件,否则最终组合数会成为一个很大的数字。这将在当前职位中尝试太多筛选条件,不能及时进入下一个职位,且会增加命中风控的概率
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<el-button type="text" font-size-12px
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span
|
||||
>这个配置是如何工作的?</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<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
|
||||
v-if="anyCombineBossRecommendFilterHasCondition"
|
||||
v-model="formContent.isSkipEmptyConditionForCombineRecommendJobFilter"
|
||||
@change="
|
||||
(v) => {
|
||||
gtagRenderer('is_skip_empty_condition_4crjf_changed', { v })
|
||||
}
|
||||
"
|
||||
>
|
||||
<span font-size-12px>跳过初始空条件,直接使用设置的条件查找职位</span>
|
||||
</el-checkbox>
|
||||
<el-checkbox v-else :model-value="false" disabled>
|
||||
<span font-size-12px>跳过初始空条件,直接使用设置的条件查找职位</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div font-size-12px>
|
||||
当前组合条件数:{{
|
||||
currentAnyCombineRecommendJobFilterCombinationCount.toLocaleString()
|
||||
}}
|
||||
<span
|
||||
v-if="currentAnyCombineRecommendJobFilterCombinationCount >= 5"
|
||||
class="color-orange"
|
||||
>不建议选择太多组合条件 -
|
||||
否则将在当前职位中尝试太多筛选条件,不能及时进入下一个职位,且会增加命中风控的概率</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<AnyCombineBossRecommendFilter v-model="formContent.anyCombineRecommendJobFilter" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="filter" mb0>
|
||||
<el-checkbox
|
||||
v-if="anyCombineBossRecommendFilterHasCondition"
|
||||
v-model="formContent.isSkipEmptyConditionForCombineRecommendJobFilter"
|
||||
@change="
|
||||
(v) => {
|
||||
gtagRenderer('is_skip_empty_condition_4crjf_changed', { v })
|
||||
}
|
||||
"
|
||||
>
|
||||
<span font-size-12px>跳过初始空条件,直接使用设置的条件查找职位</span>
|
||||
</el-checkbox>
|
||||
<el-checkbox v-else :model-value="false" disabled>
|
||||
<span font-size-12px>跳过初始空条件,直接使用设置的条件查找职位</span>
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<div font-size-12px mt10px>
|
||||
当前组合条件数:{{
|
||||
currentAnyCombineRecommendJobFilterCombinationCount.toLocaleString()
|
||||
}}
|
||||
<span
|
||||
v-if="currentAnyCombineRecommendJobFilterCombinationCount >= 5"
|
||||
class="color-orange"
|
||||
>不建议选择太多组合条件 -
|
||||
否则将在当前职位中尝试太多筛选条件,不能及时进入下一个职位,且会增加命中风控的概率</span
|
||||
>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- <el-form-item
|
||||
label="钉钉机器人 AccessToken(用于记录开聊,请勿使用公司内部群)"
|
||||
@@ -582,9 +619,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="config-section">
|
||||
<el-form-item mb0>
|
||||
<el-form-item mb0 mt20px>
|
||||
<div font-size-16px>职位详情筛选条件</div>
|
||||
</el-form-item>
|
||||
<div>
|
||||
@@ -617,7 +652,12 @@
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||
></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-menu
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr 1fr'
|
||||
}"
|
||||
>
|
||||
<el-dropdown-item
|
||||
v-for="item in expectJobFilterTemplateList"
|
||||
:key="item.name"
|
||||
@@ -925,15 +965,18 @@ import { ElForm, ElMessage } from 'element-plus'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
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 {
|
||||
calculateTotalCombinations,
|
||||
checkAnyCombineBossRecommendFilterHasCondition
|
||||
checkAnyCombineBossRecommendFilterHasCondition,
|
||||
formatStaticCombineFilters
|
||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/combineCalculator.mjs'
|
||||
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 { ArrowDown } from '@element-plus/icons-vue'
|
||||
import {
|
||||
CombineRecommendJobFilterType,
|
||||
MarkAsNotSuitOp,
|
||||
StrategyScopeOptionWhenMarkJobNotMatch,
|
||||
SalaryCalculateWay,
|
||||
@@ -944,6 +987,7 @@ import mittBus from '../../../utils/mitt'
|
||||
import CityChooser from './components/CityChooser.vue'
|
||||
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 expectJobFilterTemplateList from './expectJobFilterTemplateList'
|
||||
|
||||
const gtagRenderer = (name, params?: object) => {
|
||||
return baseGtagRenderer(name, {
|
||||
@@ -958,6 +1002,8 @@ const formContent = ref({
|
||||
dingtalkRobotAccessToken: '',
|
||||
expectCompanies: '',
|
||||
anyCombineRecommendJobFilter: {},
|
||||
combineRecommendJobFilterType: 1,
|
||||
staticCombineRecommendJobFilterConditions: [],
|
||||
expectJobNameRegExpStr: '',
|
||||
expectJobTypeRegExpStr: '',
|
||||
expectJobDescRegExpStr: '',
|
||||
@@ -999,6 +1045,16 @@ const anyCombineBossRecommendFilterHasCondition = 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(
|
||||
formContent.value.anyCombineRecommendJobFilter,
|
||||
anyCombineBossRecommendFilterHasCondition.value
|
||||
@@ -1045,6 +1101,12 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||
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 (
|
||||
res.config['boss.json']?.expectJobRegExpStr &&
|
||||
typeof res.config['boss.json']?.expectJobNameRegExpStr === 'undefined' &&
|
||||
@@ -1126,6 +1188,7 @@ const jobSourceFormItemSectionEl = ref()
|
||||
const jobDetailRegExpSectionEl = ref()
|
||||
const formRules = {
|
||||
expectJobNameRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator(_, value, cb) {
|
||||
if (!value) {
|
||||
cb()
|
||||
@@ -1134,16 +1197,17 @@ const formRules = {
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_name')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_name', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_name')
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_name', { v: value })
|
||||
}
|
||||
}
|
||||
},
|
||||
expectJobTypeRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator(_, value, cb) {
|
||||
if (!value) {
|
||||
cb()
|
||||
@@ -1152,16 +1216,17 @@ const formRules = {
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_type')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_type', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_type')
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_type', { v: value })
|
||||
}
|
||||
}
|
||||
},
|
||||
expectJobDescRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator(_, value, cb) {
|
||||
if (!value) {
|
||||
cb()
|
||||
@@ -1170,12 +1235,12 @@ const formRules = {
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_desc')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_desc', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_desc')
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_desc', { v: value })
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1215,8 +1280,14 @@ const formRules = {
|
||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||
const handleSubmit = async () => {
|
||||
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?.()
|
||||
})
|
||||
// remove the obsolete filter - expectJobRegExpStr
|
||||
formContent.value.expectJobRegExpStr = undefined
|
||||
try {
|
||||
await formRef.value!.validate()
|
||||
@@ -1322,53 +1393,6 @@ function handleExpectCompanyTemplateClicked(item) {
|
||||
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) {
|
||||
gtagRenderer('expect_job_filter_tpl_clicked', {
|
||||
name: item.name
|
||||
@@ -1619,6 +1643,17 @@ function formatJobSourceFormValueToConfig(formValue = []) {
|
||||
return it
|
||||
})
|
||||
}
|
||||
|
||||
const combineRecommendJobFilterTypeOptions = [
|
||||
{
|
||||
name: '使用自由组合条件进行遍历',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: '使用固定组合条件进行遍历',
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<RouterLink to="./GeekAutoStartChatWithBoss">
|
||||
Boss炸弹
|
||||
|
||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -129,6 +129,9 @@ importers:
|
||||
electron-updater:
|
||||
specifier: ^6.1.7
|
||||
version: 6.1.7
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
minimist:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
@@ -226,9 +229,6 @@ importers:
|
||||
js-yaml:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
normalize.css:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
@@ -4570,7 +4570,6 @@ packages:
|
||||
|
||||
/lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
dev: true
|
||||
|
||||
/lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
|
||||
resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
|
||||
|
||||
Reference in New Issue
Block a user