diff --git a/README.md b/README.md
index e728812..cfd93ae 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
# 牛人快跑 - GeekGeekRun
-**->维护者正在疯狂求职中<-**
-
一款可以帮助你在BOSS直聘上**自动批量开聊BOSS**的脚本,基于Puppeteer。
与每一位牛人站在一起
@@ -11,8 +9,8 @@
各行各业,无论你是小白还是大佬,都能通过几步简单的配置,快速开始求职!
-## TO 有求职之外其他目的朋友
-本程序的目标,是帮助求职者尽快寻得一个能让他为之**挥洒汗水**、**努力拼搏**的**事业**。如果有其它目的,请勿下载使用。
+## TO 有求职之外其他用途的朋友
+本程序的目标,是帮助求职者调研求职市场行情或寻找工作。如果有其它用途,请勿下载使用。
## 程序有哪些功能?运行逻辑是什么?怎样使用?
@@ -152,8 +150,8 @@ BOSS不明原因已读不回?简历就是投不出去?
- 本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,**不会泄露给第三方**,也不会进行除自动开聊BOSS以外的行为;**请勿向他人泄漏您的Cookie**。
- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的BOSS直聘账号**进行求职。
- 本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
-- 您所在公司可能会对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;上级/HRBP团队可能会从 IT 团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级/HRBP团队了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
-- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及上级/HRBP报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。
+- 您所在公司可能会采购上网行为监控工具或网关(例如奇安信、深信服、绿盟等厂商的产品),对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;您的上级/IT/HR 可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR 了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
+- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及您的上级/IT/HR 报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。但由于本程序开源,任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序。
- 本程序**没有内置任何付费功能**,**下载**、**使用**是**免费**的,任何人可以**免费获得**、**免费使用**。**作者没有利用本程序赚到过任何收入**。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,那**您大概率被骗了**,或者**您下载到了本程序修改版**。**本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方**。
- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。
- 请在BOSS直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请**在BOSS直聘上屏蔽当前公司及与之关联的公司**。
diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json
index 2b202d4..8eb4dfe 100644
--- a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json
+++ b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json
@@ -11,6 +11,9 @@
"staticCombineRecommendJobFilterConditions": [],
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
"expectJobRegExpStr": "",
+ "expectJobNameRegExpStr": "",
+ "expectJobTypeRegExpStr": "",
+ "expectJobDescRegExpStr": "",
"jobNotMatchStrategy": 1,
"jobNotActiveStrategy": 1,
"markAsNotActiveSelectedTimeRange": 7,
@@ -36,5 +39,6 @@
"isSageTimeEnabled": true,
"sageTimeOpTimes": 100,
"sageTimePauseMinute": 15,
- "blockCompanyNameRegExpStr": ""
+ "blockCompanyNameRegExpStr": "",
+ "fieldsForUseCommonConfig": {}
}
\ No newline at end of file
diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/common-job-condition-config.json b/packages/geek-auto-start-chat-with-boss/default-config-file/common-job-condition-config.json
new file mode 100644
index 0000000..22d37d6
--- /dev/null
+++ b/packages/geek-auto-start-chat-with-boss/default-config-file/common-job-condition-config.json
@@ -0,0 +1,12 @@
+{
+ "expectCityList": [],
+ "expectJobNameRegExpStr": "",
+ "expectJobTypeRegExpStr": "",
+ "expectJobDescRegExpStr": "",
+ "expectCompanies": [],
+ "blockCompanyNameRegExpStr": "",
+ "jobDetailRegExpMatchLogic": 1,
+ "expectSalaryCalculateWay": 1,
+ "expectSalaryLow": null,
+ "expectSalaryHigh": null
+}
\ No newline at end of file
diff --git a/packages/geek-auto-start-chat-with-boss/index.mjs b/packages/geek-auto-start-chat-with-boss/index.mjs
index 40bf6f0..eb07176 100644
--- a/packages/geek-auto-start-chat-with-boss/index.mjs
+++ b/packages/geek-auto-start-chat-with-boss/index.mjs
@@ -39,6 +39,7 @@ import {
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
import { waitForSageTimeOrJustContinue } from './sage-time.mjs'
import cityGroupData from './cityGroup.mjs'
+import { hasIntersection } from '@geekgeekrun/utils/number.mjs';
const flattedCityList = []
;(cityGroupData?.zpData?.cityGroup ?? []).forEach(it => {
const firstChar = it.firstChar
@@ -112,7 +113,15 @@ export async function initPuppeteer () {
}
}
-const targetCompanyList = readConfigFile('target-company-list.json').filter(it => !!it.trim());
+const commonJobConditionConfig = readConfigFile('common-job-condition-config.json')
+const fieldsForUseCommonConfig = readConfigFile('boss.json').fieldsForUseCommonConfig ?? {}
+
+const targetCompanyList = (
+ !fieldsForUseCommonConfig.expectCompanies ?
+ readConfigFile('target-company-list.json')
+ :
+ commonJobConditionConfig.expectCompanies
+).filter(it => !!it.trim());
const combineRecommendJobFilterType = readConfigFile('boss.json').combineRecommendJobFilterType ?? CombineRecommendJobFilterType.ANY_COMBINE
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
@@ -125,14 +134,34 @@ const expectJobRegExpStr = readConfigFile('boss.json').expectJobRegExpStr
const jobNotMatchStrategy = readConfigFile('boss.json').jobNotMatchStrategy ?? MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
const expectCityNotMatchStrategy = readConfigFile('boss.json').expectCityNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
-const expectCityList = readConfigFile('boss.json').expectCityList ?? []
+const expectCityList = (
+ !fieldsForUseCommonConfig.city ?
+ readConfigFile('boss.json').expectCityList
+ :
+ commonJobConditionConfig.expectCityList
+) ?? []
const strategyScopeOptionWhenMarkJobCityNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobCityNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
// salary
-const expectSalaryLow = parseFloat(readConfigFile('boss.json').expectSalaryLow) || null
-const expectSalaryHigh = parseFloat(readConfigFile('boss.json').expectSalaryHigh) || null
-const expectSalaryCalculateWay = readConfigFile('boss.json').expectSalaryCalculateWay ?? SalaryCalculateWay.MONTH_SALARY
+const expectSalaryLow = parseFloat(
+ !fieldsForUseCommonConfig.salary ?
+ readConfigFile('boss.json').expectSalaryLow
+ :
+ commonJobConditionConfig.expectSalaryLow
+) || null
+const expectSalaryHigh = parseFloat(
+ !fieldsForUseCommonConfig.salary ?
+ readConfigFile('boss.json').expectSalaryHigh
+ :
+ commonJobConditionConfig.expectSalaryHigh
+) || null
+const expectSalaryCalculateWay = (
+ !fieldsForUseCommonConfig.salary ?
+ readConfigFile('boss.json').expectSalaryCalculateWay
+ :
+ commonJobConditionConfig.expectSalaryCalculateWay
+) ?? SalaryCalculateWay.MONTH_SALARY
const expectSalaryNotMatchStrategy = readConfigFile('boss.json').expectSalaryNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
@@ -153,7 +182,12 @@ expectWorkExpList = Array.from(expectWorkExpListSet)
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
-let jobDetailRegExpMatchLogic = readConfigFile('boss.json').jobDetailRegExpMatchLogic ?? JobDetailRegExpMatchLogic.EVERY
+let jobDetailRegExpMatchLogic = (
+ !fieldsForUseCommonConfig.jobDetail ?
+ readConfigFile('boss.json').jobDetailRegExpMatchLogic
+ :
+ commonJobConditionConfig.jobDetailRegExpMatchLogic
+) ?? JobDetailRegExpMatchLogic.EVERY
const markAsNotActiveSelectedTimeRange = (() => {
let n = readConfigFile('boss.json').markAsNotActiveSelectedTimeRange
@@ -176,8 +210,9 @@ let {
expectJobNameRegExpStr,
expectJobTypeRegExpStr,
expectJobDescRegExpStr,
-} = readConfigFile('boss.json')
+} = !fieldsForUseCommonConfig.jobDetail ? readConfigFile('boss.json') : commonJobConditionConfig
if (
+ !fieldsForUseCommonConfig.jobDetail &&
expectJobRegExpStr &&
!expectJobNameRegExpStr &&
!expectJobTypeRegExpStr &&
@@ -247,7 +282,12 @@ const recommendJobPageUrl = `https://www.zhipin.com/web/geek/jobs`
const expectCompanySet = new Set(targetCompanyList)
const enableCompanyAllowList = Boolean(expectCompanySet.size)
-const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? ''
+const blockCompanyNameRegExpStr = (
+ !fieldsForUseCommonConfig.blockCompanyNameRegExpStr ?
+ readConfigFile('boss.json').blockCompanyNameRegExpStr
+ :
+ commonJobConditionConfig.blockCompanyNameRegExpStr
+) ?? ''
const blockCompanyNameRegExp = (() => {
if (!blockCompanyNameRegExpStr?.trim()) {
return null
@@ -947,20 +987,24 @@ async function toRecommendPage (hooks) {
function checkIfSalarySuit(salaryDesc) {
const salaryData = parseSalary(salaryDesc)
if (expectSalaryCalculateWay === SalaryCalculateWay.MONTH_SALARY) {
- if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
- return false
+ let ourSalaryInterval = [expectSalaryLow ?? null, expectSalaryHigh ?? null]
+ if (ourSalaryInterval.every(it => !isNaN(parseFloat(it)))) {
+ ourSalaryInterval = ourSalaryInterval.sort((a, b) => a - b)
}
- if (expectSalaryLow && salaryData.high < expectSalaryLow) {
- return false
- }
- } else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
+ const theirSalaryInterval = [salaryData.low ?? null, salaryData.high ?? null]
+ return hasIntersection(theirSalaryInterval, ourSalaryInterval)
+ }
+ else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
const salaryDataMonth = salaryData.month || 12
- if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
- return false
- }
- if (expectSalaryLow && (salaryData.high * salaryDataMonth) / 10 < expectSalaryLow) {
- return false
+ let ourSalaryInterval = [expectSalaryLow ?? null, expectSalaryHigh ?? null]
+ if (ourSalaryInterval.every(it => !isNaN(parseFloat(it)))) {
+ ourSalaryInterval = ourSalaryInterval.sort((a, b) => a - b)
}
+ const theirSalaryInterval = [salaryData.low ?? null, salaryData.high ?? null].map(
+ it =>
+ it === null ? null : (it * salaryDataMonth / 10)
+ )
+ return hasIntersection(theirSalaryInterval, ourSalaryInterval)
}
return true
}
diff --git a/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs b/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs
index dd9512a..0b9f555 100644
--- a/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs
+++ b/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs
@@ -11,16 +11,114 @@ import defaultLlmConf from './default-config-file/llm.json' assert { type: 'json
import defaultBossCookieStorage from './default-storage-file/boss-cookies.json' assert { type: 'json' }
import defaultBossLocalStorageStorage from './default-storage-file/boss-local-storage.json' assert { type: 'json' }
import defaultJobNotSuitReasonCodeToTextCacheStorage from './default-storage-file/job-not-suit-reason-code-to-text-cache.json' assert { type: 'json' }
-export const configFileNameList = ['boss.json', 'dingtalk.json', 'target-company-list.json', 'llm.json']
+import defaultCommonJobConditionConfig from './default-config-file/common-job-condition-config.json' assert { type: 'json' }
+export const configFileNameList = ['boss.json', 'dingtalk.json', 'target-company-list.json', 'llm.json', 'common-job-condition-config.json']
const defaultConfigFileContentMap = {
'boss.json': JSON.stringify(defaultBossConf),
'dingtalk.json': JSON.stringify(defaultDingtalkConf),
'target-company-list.json': JSON.stringify(defaultTargetCompanyListConf),
- 'llm.json': JSON.stringify(defaultLlmConf)
+ 'llm.json': JSON.stringify(defaultLlmConf),
+ 'common-job-condition-config.json': JSON.stringify(defaultCommonJobConditionConfig)
+}
+const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
+export const configFolderPath = path.join(
+ runtimeFolderPath,
+ 'config'
+)
+export const writeConfigFile = async (fileName, content, { isSync } = {}) => {
+ const filePath = path.join(configFolderPath, fileName)
+ const fileContent = JSON.stringify(content)
+ if (isSync) {
+ fs.writeFileSync(
+ filePath,
+ fileContent
+ )
+ }
+ else {
+ return fsPromise.writeFile(
+ filePath,
+ fileContent
+ )
+ }
+}
+if (
+ !fs.existsSync(
+ path.join(configFolderPath, 'common-job-condition-config.json')
+ )
+) {
+ let bossConfig = null
+ if (
+ fs.existsSync(
+ path.join(configFolderPath, 'boss.json')
+ )
+ ) {
+ fs.existsSync(
+ path.join(configFolderPath, 'boss.json')
+ )
+ try {
+ bossConfig = JSON.parse(
+ fs.readFileSync(
+ path.join(configFolderPath, 'boss.json')
+ )
+ )
+ }
+ catch {}
+ }
+ if (bossConfig) {
+ Object.keys(defaultCommonJobConditionConfig).forEach(
+ key => {
+ if (Object.hasOwn(bossConfig, key)) {
+ defaultCommonJobConditionConfig[key] = bossConfig[key]
+ }
+ }
+ )
+ let {
+ expectJobRegExpStr,
+ expectJobNameRegExpStr,
+ expectJobTypeRegExpStr,
+ expectJobDescRegExpStr,
+ } = bossConfig
+ if (
+ expectJobRegExpStr &&
+ !expectJobNameRegExpStr &&
+ !expectJobTypeRegExpStr &&
+ !expectJobDescRegExpStr
+ ) {
+ expectJobNameRegExpStr = expectJobRegExpStr
+ expectJobTypeRegExpStr = expectJobRegExpStr
+ expectJobDescRegExpStr = expectJobRegExpStr
+ }
+ Object.assign(defaultCommonJobConditionConfig, {
+ expectJobNameRegExpStr,
+ expectJobTypeRegExpStr,
+ expectJobDescRegExpStr
+ })
+ }
+ let targetCompanyList = null
+ if (
+ fs.existsSync(
+ path.join(configFolderPath, 'target-company-list.json')
+ )
+ ) {
+ targetCompanyList = JSON.parse(
+ fs.readFileSync(
+ path.join(configFolderPath, 'target-company-list.json')
+ )
+ )
+ }
+ if (targetCompanyList) {
+ defaultCommonJobConditionConfig.expectCompanies = targetCompanyList ?? []
+ }
+ writeConfigFile('common-job-condition-config.json', defaultCommonJobConditionConfig, { isSync: true })
+ if (bossConfig) {
+ if (!bossConfig.fieldsForUseCommonConfig) {
+ bossConfig.fieldsForUseCommonConfig = {}
+ }
+ writeConfigFile('boss.json', bossConfig, { isSync: true })
+ }
}
-const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
const ensureRuntimeFolderPathExist = () => {
if (!fs.existsSync(runtimeFolderPath)) {
fs.mkdirSync(runtimeFolderPath)
@@ -35,11 +133,6 @@ const ensureRuntimeFolderPathExist = () => {
}
})
}
-
-export const configFolderPath = path.join(
- runtimeFolderPath,
- 'config'
-)
export const ensureConfigFileExist = () => {
ensureRuntimeFolderPathExist()
;configFileNameList.forEach(
@@ -82,15 +175,6 @@ export const readConfigFile = (fileName) => {
return o
}
-export const writeConfigFile = async (fileName, content) => {
- const filePath = path.join(configFolderPath, fileName)
- const fileContent = JSON.stringify(content)
- return fsPromise.writeFile(
- filePath,
- fileContent
- )
-}
-
export const storageFilePath = path.join(
runtimeFolderPath,
'storage'
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 9faa277..41a7130 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "geekgeekrun-ui",
- "version": "0.15.0",
+ "version": "0.15.2",
"description": "BOSS 炸弹 - 自动开聊BOSS,助力每位打工人求职!",
"main": "./out/main/index.js",
"author": "geekgeekrun",
diff --git a/packages/ui/src/common/build-info.json b/packages/ui/src/common/build-info.json
index 2252193..7437ff1 100644
--- a/packages/ui/src/common/build-info.json
+++ b/packages/ui/src/common/build-info.json
@@ -1,7 +1,7 @@
{
- "version": "0.15.0",
- "buildVersion": 33,
- "buildTime": 1770822824486,
- "buildHash": "333b9a4558f753a7734bb8972b620d916b55418b",
+ "version": "0.15.2",
+ "buildVersion": 35,
+ "buildTime": 1770854432774,
+ "buildHash": "81f990084df491d8dfd8ddb68b3895d5a4019fe9",
"name": "geekgeekrun-ui"
}
\ No newline at end of file
diff --git a/packages/ui/src/main/features/common-job-condition.ts b/packages/ui/src/main/features/common-job-condition.ts
new file mode 100644
index 0000000..44736c6
--- /dev/null
+++ b/packages/ui/src/main/features/common-job-condition.ts
@@ -0,0 +1,28 @@
+import { ipcMain } from 'electron'
+import { createCommonJobConditionConfigWindow } from '../window/commonJobConditionConfigWindow'
+import { mainWindow } from '../window/mainWindow'
+
+let commonJobConditionConfigWindow = null
+export async function waitForCommonJobConditionDone() {
+ return new Promise((resolve, reject) => {
+ commonJobConditionConfigWindow = createCommonJobConditionConfigWindow({
+ parent: mainWindow!,
+ modal: true,
+ show: true
+ })
+ let processDone = false
+ function handler() {
+ processDone = true
+ commonJobConditionConfigWindow.close()
+ }
+ ipcMain.once('common-job-condition-config-done', handler)
+ commonJobConditionConfigWindow.on('closed', async () => {
+ ipcMain.off('common-job-condition-config-done', handler)
+ if (processDone) {
+ resolve(true)
+ } else {
+ reject(new Error('USER_CANCELLED'))
+ }
+ })
+ })
+}
diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
index bcafd3b..3937ecb 100644
--- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
+++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
@@ -2,8 +2,6 @@ import { ipcMain, shell, app, dialog, BrowserWindow } from 'electron'
import path from 'path'
import * as childProcess from 'node:child_process'
import {
- ensureConfigFileExist,
- configFileNameList,
readConfigFile,
writeConfigFile,
readStorageFile,
@@ -58,23 +56,10 @@ import {
waitForUserApproveAgreement
} from '../../../features/first-launch-notice-window'
import { getLastUsedAndAvailableBrowser } from '../../DOWNLOAD_DEPENDENCIES/utils/browser-history'
+import { waitForCommonJobConditionDone } from '../../../features/common-job-condition'
+import { ensureConfigFileExist } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
export default function initIpc() {
- ipcMain.handle('fetch-config-file-content', async () => {
- const configFileContentList = configFileNameList.map((fileName) => {
- return readConfigFile(fileName)
- })
- const result = {
- config: {}
- }
-
- configFileNameList.forEach((fileName, index) => {
- result.config[fileName] = configFileContentList[index]
- })
-
- return result
- })
-
ipcMain.handle('save-config-file-from-ui', async (ev, payload) => {
payload = JSON.parse(payload)
ensureConfigFileExist()
@@ -187,6 +172,9 @@ export default function initIpc() {
if (hasOwn(payload, 'blockCompanyNameRegMatchStrategy')) {
bossConfig.blockCompanyNameRegMatchStrategy = payload.blockCompanyNameRegMatchStrategy
}
+ if (hasOwn(payload, 'fieldsForUseCommonConfig')) {
+ bossConfig.fieldsForUseCommonConfig = payload.fieldsForUseCommonConfig
+ }
promiseArr.push(writeConfigFile('boss.json', bossConfig))
@@ -607,6 +595,12 @@ export default function initIpc() {
}
}
})
+ ipcMain.handle('common-job-condition-config', async () => {
+ await waitForCommonJobConditionDone()
+ mainWindow?.webContents.send('common-job-condition-config-updated', {
+ config: await readConfigFile('common-job-condition-config.json')
+ })
+ })
ipcMain.handle('exit-app-immediately', () => {
app.exit(0)
diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts
index c28430d..7d03628 100644
--- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts
+++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts
@@ -58,12 +58,21 @@ const rechatLlmFallback =
readConfigFile('boss.json').autoReminder?.rechatLlmFallback ??
RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION
-const expectJobTypeRegExpStr = readConfigFile('boss.json').expectJobTypeRegExpStr
+const fieldsForUseCommonConfig = readConfigFile('boss.json').fieldsForUseCommonConfig ?? {}
+const commonJobConditionConfig = readConfigFile('common-job-condition-config.json') ?? {}
+const expectJobTypeRegExpStr =
+ (!fieldsForUseCommonConfig.jobDetail ? readConfigFile('boss.json') : commonJobConditionConfig)
+ ?.expectJobTypeRegExpStr ?? ''
const onlyRemindBossWithExpectJobType =
readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ??
!!expectJobTypeRegExpStr
-const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? ''
+const blockCompanyNameRegExpStr =
+ (!fieldsForUseCommonConfig.blockCompanyNameRegExpStr
+ ? readConfigFile('boss.json')
+ : commonJobConditionConfig
+ )?.blockCompanyNameRegExpStr ?? ''
+
const blockCompanyNameRegExp = (() => {
if (!blockCompanyNameRegExpStr?.trim()) {
return null
diff --git a/packages/ui/src/main/utils/initPublicIpc.ts b/packages/ui/src/main/utils/initPublicIpc.ts
index 305f99e..3d4adfe 100644
--- a/packages/ui/src/main/utils/initPublicIpc.ts
+++ b/packages/ui/src/main/utils/initPublicIpc.ts
@@ -5,8 +5,10 @@ import os from 'node:os'
import fs from 'node:fs'
import {
ensureStorageFileExist,
- readStorageFile,
- writeStorageFile
+ writeStorageFile,
+ configFileNameList,
+ readConfigFile,
+ readStorageFile
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
export default function initPublicIpc() {
@@ -110,4 +112,19 @@ export default function initPublicIpc() {
}
return null
})
+
+ ipcMain.handle('fetch-config-file-content', async () => {
+ const configFileContentList = configFileNameList.map((fileName) => {
+ return readConfigFile(fileName)
+ })
+ const result = {
+ config: {}
+ }
+
+ configFileNameList.forEach((fileName, index) => {
+ result.config[fileName] = configFileContentList[index]
+ })
+
+ return result
+ })
}
diff --git a/packages/ui/src/main/window/commonJobConditionConfigWindow.ts b/packages/ui/src/main/window/commonJobConditionConfigWindow.ts
new file mode 100644
index 0000000..c4daaa9
--- /dev/null
+++ b/packages/ui/src/main/window/commonJobConditionConfigWindow.ts
@@ -0,0 +1,55 @@
+import { BrowserWindow, ipcMain } from 'electron'
+import path from 'path'
+import { writeConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
+
+export let commonJobConditionConfigWindow: BrowserWindow | null = null
+export function createCommonJobConditionConfigWindow(
+ opt?: Electron.BrowserWindowConstructorOptions
+): BrowserWindow {
+ // Create the browser window.
+ if (commonJobConditionConfigWindow) {
+ commonJobConditionConfigWindow!.show()
+ }
+ commonJobConditionConfigWindow = new BrowserWindow({
+ width: 1024,
+ height: 768,
+ resizable: false,
+ show: false,
+ autoHideMenuBar: true,
+ webPreferences: {
+ preload: path.join(__dirname, '../preload/index.js'),
+ sandbox: false
+ },
+ ...opt
+ })
+
+ commonJobConditionConfigWindow.on('ready-to-show', () => {
+ commonJobConditionConfigWindow!.show()
+ })
+
+ // HMR for renderer base on electron-vite cli.
+ // Load the remote URL for development or the local html file for production.
+ if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) {
+ commonJobConditionConfigWindow.loadURL(
+ process.env['ELECTRON_RENDERER_URL'] + '#/commonJobConditionConfig'
+ )
+ } else {
+ commonJobConditionConfigWindow.loadURL(
+ 'file://' + path.join(__dirname, '../renderer/index.html') + '#/commonJobConditionConfig'
+ )
+ }
+
+ commonJobConditionConfigWindow!.once('closed', () => {
+ commonJobConditionConfigWindow = null
+ })
+
+ ipcMain.handle('save-common-job-condition-config', async (_ev, payload) => {
+ await writeConfigFile('common-job-condition-config.json', payload)
+ commonJobConditionConfigWindow!.close()
+ })
+ commonJobConditionConfigWindow!.once('closed', () => {
+ ipcMain.removeHandler('save-common-job-condition-config')
+ })
+
+ return commonJobConditionConfigWindow!
+}
diff --git a/packages/ui/src/renderer/src/page/CommonJobConditionConfig/index.vue b/packages/ui/src/renderer/src/page/CommonJobConditionConfig/index.vue
new file mode 100644
index 0000000..4374250
--- /dev/null
+++ b/packages/ui/src/renderer/src/page/CommonJobConditionConfig/index.vue
@@ -0,0 +1,733 @@
+
+
+
+
+ 公共职位筛选条件
+
+
+
+ 期望投递公司
+
逗号分隔,不区分大小写;输入框留空表示不筛选
+
+
+ 公司列表模板
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+ 不期望投递公司正则
正则表达式,不区分大小写;输入框留空表示不筛选;优先级高于上方“期望投递公司”
请小心验证你编写的正则,填写太过于宽泛的正则(例如`.*`)将导致任何职位都不会开聊
+
+
+ 公司列表模板
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+
+
+
工作地
+
+
+
+
+
+
+
当前已选择城市:
+
+
+ {{ it }}
+
+
+
+
+
当前未选择任何期望城市,将不会按照城市进行筛选
+
+
+ {
+ // isDialogVisible = true
+ showDialog()
+ gtagRenderer('choose_city_entry_button_clicked')
+ }
+ "
+ >选择城市
+ 清空已选择的所有城市
+
+
+
+
+
+
+
+
+
+
+ 薪资(仅支持按月计算薪资的职位;非按月计算薪资职位(例如兼职职位、实习职位)将直接跳过)
+
+
+
+
+
+
薪资筛选方式
+
+ {{ op.name }}
+
+
+
+
+
+
期望薪资范围
+
+ {
+ gtagRenderer('expect_salary_low_changed')
+ ensureSalaryRangeCorrect({ formContent })
+ }
+ "
+ >
+ 下限
+
+
+ k
+ W
+
+
+
+ -
+ {
+ gtagRenderer('expect_salary_high_changed')
+ ensureSalaryRangeCorrect({ formContent })
+ }
+ "
+ >
+ 上限
+
+
+ k
+ W
+
+
+
+
+
+
+
+
+
薪资范围满足以下条件的职位将会被匹配
+
+
+
+
+
+ |
+ {{ text }}
+ |
+
+
+ |
+ {{
+ formContent.expectSalaryLow
+ ? ((formContent.expectSalaryLow / m) * 10).toFixed(2)
+ : '无下限'
+ }}k
+ |
+
+ {{
+ formContent.expectSalaryHigh
+ ? ((formContent.expectSalaryHigh / m) * 10).toFixed(2)
+ : '无上限'
+ }}k
+ |
+ {{ m }}薪 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
期望职位信息
+
+
+ 职位详情筛选模板(按职类区分)
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+ 职位名称/类型/描述 正则匹配筛选逻辑
+ gtagRenderer('job_detail_re_ml_change', { value })"
+ >
+ {{ op.name }}
+
+
+
+
+
+
+
+
+
+ 上方“职位名称/类型/描述
+ 正则匹配筛选逻辑”配置,你可以自行决定如下三个正则“所有正则匹配时才认为职位匹配”还是“任一正则匹配时即认为职位匹配”。
+
+
+ -
+ 当选择“所有正则匹配时才认为职位匹配”规则时,如果你留空某个输入框,表示任何职位一定匹配这个条件。
+
+ -
+ 当选择“任一正则匹配时即认为职位匹配”规则时,如果你留空某个输入框,表示任何职位一定不匹配这个条件。
+
+
+
请注意,如果
三个输入框均留空,无论上方“职位名称/类型/描述 正则匹配筛选逻辑”配置是什么,都表示
列表中出现的任意职位都将认为同时匹配这三个条件(即不根据职位名称/类型/描述进行筛选)。
+ 因此,可以按照如下场景填写你对于期望职位的筛选条件:
+
+ -
+ 如果你只考虑工作类型,请填写“职位类型正则”输入框,其余两个输入框清空。这可以确保求职方向基本正确。
+
+ -
+ 如果你着重关注职位描述,请填写“职位描述正则”,其余两个输入框酌情填写。
+
+ -
+ 如果你想开聊列表里的推荐的任意职位,不根据职位名称/类型/描述进行筛选,请清空这三个输入框。
+
+
+
+ 你可以在右侧"职位详情筛选模板"选择一个模板,并在选中模板基础上尝试修改
+
+
+ “职位类型正则”填写过程中请注意,“职位类型”是由BOSS直聘预定义好的一系列职位分类,因此请按照这个分类来编写正则。
+
+
+
+
+
+ 如下三个输入框工作机制是怎样的?怎样填写?误伤/误投如何排查?
+
+
+
+ 职位名称正则(不区分大小写)
+
+
+
+ {{ getJobDetailRegExpMatchLogicConfig({ formContent }).logicText }}
+
+
+
+ 职位类型正则(推荐填写,不区分大小写)
+
+
+
+
+ {{ getJobDetailRegExpMatchLogicConfig({ formContent }).logicText }}
+
+
+ 职位描述正则(不区分大小写)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/renderer/src/page/FirstRunReadme/index.vue b/packages/ui/src/renderer/src/page/FirstRunReadme/index.vue
index 4430ea4..4570008 100644
--- a/packages/ui/src/renderer/src/page/FirstRunReadme/index.vue
+++ b/packages/ui/src/renderer/src/page/FirstRunReadme/index.vue
@@ -52,18 +52,20 @@
>)。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
- 您所在公司可能会对您的计算机终端或网络进行监控,从而审计、跟踪您的行为;上级 / HRBP 团队可能会从 IT
- 团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级 / HRBP
- 团队了解到您正在求职,建议您监控,从而审计、跟踪您的行为;您的上级/IT/HR
+ 可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR
+ 了解到您正在求职,建议您不要在您所在公司提供的计算机终端或网络上使用本程序。
本程序尊重您的隐私,不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及上级 / HRBP
- 团队报告您的求职行为、不会向猎头公司泄露您的信息不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及您的上级/IT/HR报告您的求职行为、不会向猎头公司泄露您的信息。但由于本程序开源,任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序。
diff --git a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/common.ts b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/common.ts
new file mode 100644
index 0000000..53fed60
--- /dev/null
+++ b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/common.ts
@@ -0,0 +1,282 @@
+import { SalaryCalculateWay, JobDetailRegExpMatchLogic } from '@geekgeekrun/sqlite-plugin/src/enums'
+import sampleCompanyList from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/sample-company-list.json'
+import { nextTick } from 'vue'
+
+export function isJobDetailRegExpEmpty({ formContent }) {
+ return [
+ formContent.expectJobDescRegExpStr,
+ formContent.expectJobNameRegExpStr,
+ formContent.expectJobTypeRegExpStr
+ ]
+ .map((it) => Boolean(it?.trim()))
+ .every((it) => it === false)
+}
+
+export function getJobDetailRegExpMatchLogicConfig({ formContent }) {
+ const result = {
+ logicText: '-',
+ inputPlaceholderText: '-'
+ }
+ if (formContent.jobDetailRegExpMatchLogic === JobDetailRegExpMatchLogic.EVERY) {
+ Object.assign(result, {
+ logicText: '且',
+ inputPlaceholderText: 'true'
+ })
+ }
+ if (formContent.jobDetailRegExpMatchLogic === JobDetailRegExpMatchLogic.SOME) {
+ Object.assign(result, {
+ logicText: '或',
+ inputPlaceholderText: 'false'
+ })
+ }
+
+ if (isJobDetailRegExpEmpty({ formContent })) {
+ result.inputPlaceholderText = 'true'
+ }
+ return result
+}
+
+export const expectSalaryCalculateWayOption = [
+ {
+ name: '月薪(单位为 千元 - 即“k”)',
+ value: SalaryCalculateWay.MONTH_SALARY
+ },
+ {
+ name: '总包(单位为 万元 - 即“W”)',
+ value: SalaryCalculateWay.ANNUAL_PACKAGE
+ }
+]
+
+export function ensureSalaryRangeCorrect({ formContent }) {
+ if (!formContent.expectSalaryHigh || isNaN(parseFloat(formContent.expectSalaryHigh))) {
+ formContent.expectSalaryHigh = null
+ } else {
+ formContent.expectSalaryHigh = parseFloat(formContent.expectSalaryHigh.toFixed(2))
+ }
+ if (!formContent.expectSalaryLow || isNaN(parseFloat(formContent.expectSalaryLow))) {
+ formContent.expectSalaryLow = null
+ } else {
+ formContent.expectSalaryLow = parseFloat(formContent.expectSalaryLow.toFixed(2))
+ }
+
+ if (
+ formContent.expectSalaryLow &&
+ formContent.expectSalaryHigh &&
+ formContent.expectSalaryLow > formContent.expectSalaryHigh
+ ) {
+ formContent.expectSalaryHigh = formContent.expectSalaryLow
+ }
+}
+
+export function getRuleOfExpectJobNameRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl }) {
+ return (_, value, cb) => {
+ if (!value) {
+ cb()
+ gtagRenderer('empty_reg_exp_for_expect_job_name')
+ return
+ }
+ try {
+ new RegExp(value, 'ig')
+ 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', { v: value })
+ }
+ }
+}
+
+export function getRuleOfExpectJobTypeRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl }) {
+ return (_, value, cb) => {
+ if (!value) {
+ cb()
+ gtagRenderer('empty_reg_exp_for_expect_job_type')
+ return
+ }
+ try {
+ new RegExp(value, 'ig')
+ 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', { v: value })
+ }
+ }
+}
+
+export function getRuleOfExpectJobDescRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl }) {
+ return (_, value, cb) => {
+ if (!value) {
+ cb()
+ gtagRenderer('empty_reg_exp_for_expect_job_desc')
+ return
+ }
+ try {
+ new RegExp(value, 'ig')
+ 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', { v: value })
+ }
+ }
+}
+
+export function getRuleOfBlockCompanyNameRegExpStr({
+ gtagRenderer,
+ blockCompanyNameRegExpSectionEl
+}) {
+ return (_, value, cb) => {
+ if (!value) {
+ cb()
+ gtagRenderer('empty_reg_exp_for_bcn')
+ return
+ }
+ try {
+ new RegExp(value, 'ig')
+ gtagRenderer('valid_reg_exp_for_bcn', { v: value })
+ cb()
+ } catch (err) {
+ cb(new Error(`正则无效:${err?.message}`))
+ blockCompanyNameRegExpSectionEl.value?.scrollIntoViewIfNeeded()
+ gtagRenderer('invalid_reg_exp_for_bcn', { v: value })
+ }
+ }
+}
+
+export const expectCompanyTemplateList = [
+ {
+ name: '不限公司(随便投)',
+ value: ''
+ },
+ {
+ name: '示例公司',
+ value: sampleCompanyList.join(',')
+ },
+ {
+ name: '大厂及关联企业',
+ value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,滴滴,嘀嘀,巨量引擎,小桔,网易,有道,腾讯,酷狗,酷我,阅文,搜狗,小鹅通,富途,京东,沃东天骏,达达,达冠,京邦达,百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,智图,长地万方,瑞图万方,道道通,小熊博望,理想,蔚来,顺丰,丰巢,中通,圆通,申通,跨越,讯飞,同程,艺龙,马蜂窝,贝壳,自如,链家,我爱我家,相寓,多点,金山,小米,猎豹,新浪,微博,阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,高德,LAZADA,来赞达,飞猪,菜鸟,哈啰,钉钉,乌鸫,饿了么,美团,三快,猫眼,快手,映客,小红书,行吟,奇虎,360,三六零,鸿盈,奇富,奇元,亚信,启明星辰,奇安信,深信服,长亭,绿盟,天融信,商汤,SenseTime,大华,海康威视,hikvision,汽车之家,车好多,瓜子,易车,昆仑万维,昆仑天工,闲徕,趣加,FunPlus,完美,马上消费,轻松,水滴,白龙马,58,更赢,车欢欢,五八,红布林,致美,快狗,天鹅到家,转转,美餐,知乎,智者四海,易点云,搜狐,用友,畅捷通,猿辅导,小猿,猿力,好未来,学而思,希望学,新东方,东方甄选,东方优选,作业帮,高途,跟谁学,学科网,天学网,一起教育,一起作业,美术宝,火花思维,粉笔,51talk,爱学习,高思,老虎国际,一心向上,向上一意,联想,拉勾,乐视,欢聚,竞技世界,拼多多,寻梦,从鲸,TEMU,得物,有赞,Moka,希瑞亚斯,北森,OPPO,欧珀,vivo,维沃,小天才,步步高,读书郎,货拉拉,陌陌,探探,Shopee,虾皮,首汽租车,GoFun,神州租车,天眼查,旷视,小冰,美图,智谱华章,MiniMax,石头科技,迅雷,TP,锐捷,Tenda,腾达,斐讯,希音,SHEIN,稀宇,深言,百川智能,与爱为舞,牵手,Grab,爱回收,洋钱罐,瓴岳,得到,思维造物,地平线,咪咕,翼支付,电信,天翼,联通,蓝湖,墨刀,海尔,美的,米哈游,传音,同花顺,国美,TCL`
+ },
+ {
+ name: '阿里系',
+ value: `阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,飞猪,乌鸫,饿了么,LAZADA,来赞达,菜鸟,哈啰,钉钉,高德,白龙马,新浪,微博`
+ },
+ {
+ name: '字节(头条/抖音)系',
+ value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,巨量引擎`
+ },
+ {
+ name: '百度系',
+ value: `百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,作业帮,智图,长地万方,瑞图万方,道道通,小熊博望`
+ },
+ {
+ name: '腾讯系',
+ value: `腾讯,酷狗,酷我,阅文,搜狗,小鹅通,富途,京东,沃东天骏,达达,达冠,京邦达,美团,三快,猫眼,快手,拼多多,寻梦,从鲸,TEMU,Shopee,虾皮,滴滴,嘀嘀,小桔,转转`
+ },
+ {
+ name: '外包、劳务派遣企业',
+ value: `青钱,软通动力,南天,睿服,中电金信,佰钧成,云链,博彦,汉克时代,柯莱特,拓保,亿达信息,纬创,微创,微澜,诚迈科技,法本,兆尹,诚迈,联合永道,新致软件,宇信科技,华为,德科,FESCO,科锐,科之锐`
+ }
+]
+
+export const blockCompanyNameRegExpTemplateList = [
+ {
+ name: '不限公司(不按照公司名称来标注不合适)',
+ value: ''
+ },
+ {
+ name: '外包、劳务派遣企业',
+ value: `青钱|软通动力|南天|睿服|中电金信|佰钧成|云链|博彦|汉克时代|柯莱特|拓保|亿达信息|纬创|微创|微澜|诚迈科技|法本|兆尹|诚迈|联合永道|新致软件|宇信科技|华为|德科|FESCO|科锐|科之锐`
+ },
+ {
+ name: '京东及相关公司',
+ value: '京东|沃东天骏|达达|达冠|京邦达'
+ }
+]
+
+export function getHandlerForExpectCompanyTemplateClicked({ gtagRenderer, formContent }) {
+ return function handleExpectCompanyTemplateClicked(item) {
+ gtagRenderer('expect_company_tpl_clicked', {
+ name: item.name
+ })
+ formContent.value.expectCompanies = item.value
+ }
+}
+
+export function getHandlerForExpectJobFilterTemplateClicked({ gtagRenderer, formContent }) {
+ return function handleExpectJobFilterTemplateClicked(item) {
+ gtagRenderer('expect_job_filter_tpl_clicked', {
+ name: item.name
+ })
+ Object.assign(formContent.value, {
+ ...item.config
+ })
+ }
+}
+
+export function getHandlerForBlockCompanyNameRegExpTemplateClicked({ gtagRenderer, formContent }) {
+ return function handleBlockCompanyNameRegExpTemplateClicked(item) {
+ gtagRenderer('bcn_reg_exp_tpl_clicked', {
+ name: item.name
+ })
+ formContent.value.blockCompanyNameRegExpStr = item.value
+ }
+}
+
+export const jobDetailRegExpMatchLogicOptions = [
+ {
+ name: '“且”模式 - 所有正则匹配时才认为职位匹配',
+ value: JobDetailRegExpMatchLogic.EVERY
+ },
+ {
+ name: '“或”模式 - 任一正则匹配时即认为职位匹配',
+ value: JobDetailRegExpMatchLogic.SOME
+ }
+]
+
+export function getHandlerForExpectSalaryCalculateWayChanged({ gtagRenderer, formContent }) {
+ return async function handleExpectSalaryCalculateWayChanged(value) {
+ gtagRenderer('expect_salary_calculate_way_changed', { value })
+
+ await nextTick()
+ // convert annual package to month salary as 12-month
+ if (value === SalaryCalculateWay.MONTH_SALARY) {
+ if (formContent.value.expectSalaryHigh) {
+ formContent.value.expectSalaryHigh = Number(
+ ((formContent.value.expectSalaryHigh * 10) / 12).toFixed(2)
+ )
+ }
+ if (formContent.value.expectSalaryLow) {
+ formContent.value.expectSalaryLow = Number(
+ ((formContent.value.expectSalaryLow * 10) / 12).toFixed(2)
+ )
+ }
+ return
+ }
+ // convert month salary to annual package as 12-month
+ else if (value === SalaryCalculateWay.ANNUAL_PACKAGE) {
+ if (formContent.value.expectSalaryHigh) {
+ formContent.value.expectSalaryHigh = Number(
+ ((formContent.value.expectSalaryHigh / 10) * 12).toFixed(2)
+ )
+ }
+ if (formContent.value.expectSalaryLow) {
+ formContent.value.expectSalaryLow = Number(
+ ((formContent.value.expectSalaryLow / 10) * 12).toFixed(2)
+ )
+ }
+ return
+ }
+ }
+}
+
+export const normalizeCommaSplittedStr = (str) => {
+ return str
+ .split(/,|,/)
+ .map((it) => it.trim())
+ .filter(Boolean)
+ .join(',')
+}
diff --git a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue
index 74ee323..c2d39c6 100644
--- a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue
+++ b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue
@@ -241,7 +241,15 @@
职位列表筛选条件
-
+
逗号分隔,不区分大小写;输入框留空表示不筛选
-
+
公司列表模板
@@ -286,14 +297,50 @@
-
+
+
+ 使用在“公共职位筛选条件”中设置的值
+ 编辑公共职位筛选条件
+ 填入公共职位筛选条件的值
+
+
+
+
你编写的正则,填写太过于宽泛的正则(例如`.*`)将导致任何职位都不会开聊
-
+
公司列表模板
@@ -351,31 +401,81 @@
gap: '10px'
}"
>
-
-
+
+ 使用在“公共职位筛选条件”中设置的值
+ 编辑公共职位筛选条件
+ 填入公共职位筛选条件的值
+
+
-
+ mb0
+ w-full
+ >
+
+
+
+
@@ -413,7 +513,11 @@
gap: '10px'
}"
>
-
+
-
+
+ 使用在“公共职位筛选条件”中设置的值
+ 编辑公共职位筛选条件
+ 填入公共职位筛选条件的值
+
+
当前已选择城市:
@@ -463,10 +589,35 @@
+
+
+
+
当前已选择城市:
+
+
+ {{ it }}
+
+
+
+
+
当前未选择任何期望城市,将不会按照城市进行筛选
+
+
+
-
-
-
薪资筛选方式
-
- {{ op.name }}
+ 使用在“公共职位筛选条件”中设置的值
+ 编辑公共职位筛选条件
+ 填入公共职位筛选条件的值
+
+
+
+
-
-
-
-
期望薪资范围
-
- {
- gtagRenderer('expect_salary_low_changed')
- ensureSalaryRangeCorrect()
- }
- "
- >
- 下限
-
-
- k
- W
-
-
-
- -
- {
- gtagRenderer('expect_salary_high_changed')
- ensureSalaryRangeCorrect()
- }
- "
- >
- 上限
-
-
- k
- W
-
-
-
-
-
-
-
-
-
薪资范围满足以下条件的职位将会被匹配
-
-
-
{{ op.name }}
-
-
-
+
+
+
+
+ 期望薪资范围
+
+ {
+ gtagRenderer('expect_salary_low_changed')
+ ensureSalaryRangeCorrect({ formContent })
+ }
+ "
+ >
+ 下限
+
+
+ k
- {{ text }}
- |
-
-
- |
- {{
- formContent.expectSalaryLow
- ? ((formContent.expectSalaryLow / m) * 10).toFixed(2)
- : '无下限'
- }}k
- |
-
- {{
- formContent.expectSalaryHigh
- ? ((formContent.expectSalaryHigh / m) * 10).toFixed(2)
- : '无上限'
- }}k
- |
- {{ m }}薪 |
-
-
-
-
+
W
+
+
+
+ -
+
{
+ gtagRenderer('expect_salary_high_changed')
+ ensureSalaryRangeCorrect({ formContent })
+ }
+ "
+ >
+ 上限
+
+
+ k
+ W
+
+
+
-
-
+
+
+
+
薪资范围满足以下条件的职位将会被匹配
+
+
+
+
+
+ |
+ {{ text }}
+ |
+
+
+ |
+ {{
+ formContent.expectSalaryLow
+ ? ((formContent.expectSalaryLow / m) * 10).toFixed(2)
+ : '无下限'
+ }}k
+ |
+
+ {{
+ formContent.expectSalaryHigh
+ ? ((formContent.expectSalaryHigh / m) * 10).toFixed(2)
+ : '无上限'
+ }}k
+ |
+ {{ m }}薪 |
+
+
+
+
+
+
+
+
+
+
+
+
+
薪资筛选方式
+
+ {{ op.name }}
+
+
+
+
+
+
期望薪资范围
+
+
+ 下限
+
+
+ k
+ W
+
+
+
+ -
+
+ 上限
+
+
+ k
+ W
+
+
+
+
+
+
+
+
+
薪资范围满足以下条件的职位将会被匹配
+
+
+
+
+
+ |
+ {{ text }}
+ |
+
+
+ |
+ {{
+ commonJobConditionConfig.expectSalaryLow
+ ? (
+ (commonJobConditionConfig.expectSalaryLow / m) *
+ 10
+ ).toFixed(2)
+ : '无下限'
+ }}k
+ |
+
+ {{
+ commonJobConditionConfig.expectSalaryHigh
+ ? (
+ (commonJobConditionConfig.expectSalaryHigh / m) *
+ 10
+ ).toFixed(2)
+ : '无上限'
+ }}k
+ |
+ {{ m }}薪 |
+
+
+
+
+
+
+
+
+
-
+
职位详情筛选模板(按职类区分)
-
+
+ 使用在“公共职位筛选条件”中设置的值
+ 编辑公共职位筛选条件
+ 填入公共职位筛选条件的值
+
+
职位名称/类型/描述 正则匹配筛选逻辑
+
+ 职位名称/类型/描述 正则匹配筛选逻辑
+
+ {{ op.name }}
+
+
+
职位名称正则(不区分大小写)
+
+ 职位名称正则(不区分大小写)
+
+
- {{ getJobDetailRegExpMatchLogicConfig().logicText }}
+ {{
+ getJobDetailRegExpMatchLogicConfig({
+ formContent: !formContent.fieldsForUseCommonConfig.jobDetail
+ ? formContent
+ : commonJobConditionConfig
+ }).logicText
+ }}
-
+
职位类型正则(推荐填写,不区分大小写)
+
+
+ 职位类型正则(推荐填写,不区分大小写)
+
+
+
- {{ getJobDetailRegExpMatchLogicConfig().logicText }}
+ {{
+ getJobDetailRegExpMatchLogicConfig({
+ formContent: !formContent.fieldsForUseCommonConfig.jobDetail
+ ? formContent
+ : commonJobConditionConfig
+ }).logicText
+ }}
-
+
职位描述正则(不区分大小写)
+
+ 职位描述正则(不区分大小写)
+
+
import { computed, onBeforeUnmount, ref, watch, nextTick, onUnmounted } from 'vue'
import { ElForm, ElMessage } from 'element-plus'
-import { QuestionFilled } from '@element-plus/icons-vue'
+import { QuestionFilled, ArrowDown } 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'
@@ -1258,8 +1727,6 @@ import {
formatStaticCombineFilters
} from '@geekgeekrun/geek-auto-start-chat-with-boss/combineCalculator.mjs'
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
-import sampleCompanyList from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/sample-company-list.json'
-import { ArrowDown } from '@element-plus/icons-vue'
import {
CombineRecommendJobFilterType,
MarkAsNotSuitOp,
@@ -1275,7 +1742,25 @@ import JobSourceDragOrderer from '../../../features/JobSourceDragOrderer/index.v
import expectJobFilterTemplateList from './expectJobFilterTemplateList'
import RunningOverlay from '@renderer/features/RunningOverlay/index.vue'
import { RUNNING_STATUS_ENUM } from '../../../../../common/enums/auto-start-chat'
-
+import {
+ getJobDetailRegExpMatchLogicConfig,
+ isJobDetailRegExpEmpty,
+ expectSalaryCalculateWayOption,
+ ensureSalaryRangeCorrect,
+ getRuleOfExpectJobNameRegExpStr,
+ getRuleOfExpectJobDescRegExpStr,
+ getRuleOfBlockCompanyNameRegExpStr,
+ expectCompanyTemplateList,
+ blockCompanyNameRegExpTemplateList,
+ getHandlerForExpectCompanyTemplateClicked,
+ getHandlerForExpectJobFilterTemplateClicked,
+ getHandlerForBlockCompanyNameRegExpTemplateClicked,
+ getRuleOfExpectJobTypeRegExpStr,
+ jobDetailRegExpMatchLogicOptions,
+ getHandlerForExpectSalaryCalculateWayChanged,
+ normalizeCommaSplittedStr
+} from './common'
+const { ipcRenderer } = window.electron
const gtagRenderer = (name, params?: object) => {
return baseGtagRenderer(name, {
scene: 'gascwb-config',
@@ -1327,7 +1812,8 @@ const formContent = ref({
sageTimeOpTimes: 100,
sageTimePauseMinute: 15,
blockCompanyNameRegExpStr: '',
- blockCompanyNameRegMatchStrategy: MarkAsNotSuitOp.NO_OP
+ blockCompanyNameRegMatchStrategy: MarkAsNotSuitOp.NO_OP,
+ fieldsForUseCommonConfig: {}
})
const anyCombineBossRecommendFilterHasCondition = computed(() => {
@@ -1451,7 +1937,7 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
formContent.value.expectSalaryLow = parseFloat(res.config['boss.json'].expectSalaryLow) || null
formContent.value.expectSalaryHigh = parseFloat(res.config['boss.json'].expectSalaryHigh) || null
- ensureSalaryRangeCorrect()
+ ensureSalaryRangeCorrect({ formContent })
// work exp
formContent.value.expectWorkExpList =
@@ -1489,6 +1975,31 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
res.config['boss.json'].blockCompanyNameRegExpStr?.trim() ?? ''
formContent.value.blockCompanyNameRegMatchStrategy =
res.config['boss.json'].blockCompanyNameRegMatchStrategy ?? MarkAsNotSuitOp.NO_OP
+ formContent.value.fieldsForUseCommonConfig =
+ res.config['boss.json']?.fieldsForUseCommonConfig ?? {}
+
+ commonJobConditionConfig.value = {
+ expectJobNameRegExpStr:
+ res.config['common-job-condition-config.json']?.expectJobNameRegExpStr ?? '',
+ expectJobTypeRegExpStr:
+ res.config['common-job-condition-config.json']?.expectJobTypeRegExpStr ?? '',
+ expectJobDescRegExpStr:
+ res.config['common-job-condition-config.json']?.expectJobDescRegExpStr ?? '',
+ jobDetailRegExpMatchLogic:
+ res.config['common-job-condition-config.json']?.jobDetailRegExpMatchLogic ??
+ JobDetailRegExpMatchLogic.EVERY,
+ expectCompanies: (res.config['common-job-condition-config.json']?.expectCompanies ?? []).join(
+ ','
+ ),
+ blockCompanyNameRegExpStr:
+ res.config['common-job-condition-config.json']?.blockCompanyNameRegExpStr ?? '',
+ expectSalaryCalculateWay:
+ res.config['common-job-condition-config.json']?.expectSalaryCalculateWay ??
+ SalaryCalculateWay.MONTH_SALARY,
+ expectSalaryLow: res.config['common-job-condition-config.json']?.expectSalaryLow ?? null,
+ expectSalaryHigh: res.config['common-job-condition-config.json']?.expectSalaryHigh ?? null,
+ expectCityList: res.config['common-job-condition-config.json']?.expectCityList ?? []
+ }
})
const jobSourceFormItemSectionEl = ref()
@@ -1497,60 +2008,15 @@ const blockCompanyNameRegExpSectionEl = ref()
const formRules = {
expectJobNameRegExpStr: {
trigger: 'blur',
- validator(_, value, cb) {
- if (!value) {
- cb()
- gtagRenderer('empty_reg_exp_for_expect_job_name')
- return
- }
- try {
- new RegExp(value, 'ig')
- 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', { v: value })
- }
- }
+ validator: getRuleOfExpectJobNameRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
},
expectJobTypeRegExpStr: {
trigger: 'blur',
- validator(_, value, cb) {
- if (!value) {
- cb()
- gtagRenderer('empty_reg_exp_for_expect_job_type')
- return
- }
- try {
- new RegExp(value, 'ig')
- 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', { v: value })
- }
- }
+ validator: getRuleOfExpectJobTypeRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
},
expectJobDescRegExpStr: {
trigger: 'blur',
- validator(_, value, cb) {
- if (!value) {
- cb()
- gtagRenderer('empty_reg_exp_for_expect_job_desc')
- return
- }
- try {
- new RegExp(value, 'ig')
- 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', { v: value })
- }
- }
+ validator: getRuleOfExpectJobDescRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
},
__jobSourceList: {
trigger: null,
@@ -1611,22 +2077,7 @@ const formRules = {
},
blockCompanyNameRegExpStr: {
trigger: 'blur',
- validator(_, value, cb) {
- if (!value) {
- cb()
- gtagRenderer('empty_reg_exp_for_bcn')
- return
- }
- try {
- new RegExp(value, 'ig')
- gtagRenderer('valid_reg_exp_for_bcn', { v: value })
- cb()
- } catch (err) {
- cb(new Error(`正则无效:${err?.message}`))
- blockCompanyNameRegExpSectionEl.value?.scrollIntoViewIfNeeded()
- gtagRenderer('invalid_reg_exp_for_bcn', { v: value })
- }
- }
+ validator: getRuleOfBlockCompanyNameRegExpStr({ gtagRenderer, blockCompanyNameRegExpSectionEl })
}
}
@@ -1707,7 +2158,7 @@ const handleSave = async () => {
opTimes: formContent.value.sageTimeOpTimes
})
})
- normalizeExpectCompanies()
+ formContent.value.expectCompanies = normalizeCommaSplittedStr(formContent.value.expectCompanies)
try {
await formRef.value!.validate()
} catch (err) {
@@ -1729,64 +2180,15 @@ const handleSave = async () => {
gtagRenderer('config_saved')
}
-const normalizeExpectCompanies = () => {
- formContent.value.expectCompanies = formContent.value.expectCompanies
- .split(/,|,/)
- .map((it) => it.trim())
- .filter(Boolean)
- .join(',')
-}
+const handleExpectCompanyTemplateClicked = getHandlerForExpectCompanyTemplateClicked({
+ gtagRenderer,
+ formContent
+})
-const expectCompanyTemplateList = [
- {
- name: '不限公司(随便投)',
- value: ''
- },
- {
- name: '示例公司',
- value: sampleCompanyList.join(',')
- },
- {
- name: '大厂及关联企业',
- value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,滴滴,嘀嘀,巨量引擎,小桔,网易,有道,腾讯,酷狗,酷我,阅文,搜狗,小鹅通,富途,京东,沃东天骏,达达,达冠,京邦达,百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,智图,长地万方,瑞图万方,道道通,小熊博望,理想,蔚来,顺丰,丰巢,中通,圆通,申通,跨越,讯飞,同程,艺龙,马蜂窝,贝壳,自如,链家,我爱我家,相寓,多点,金山,小米,猎豹,新浪,微博,阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,高德,LAZADA,来赞达,飞猪,菜鸟,哈啰,钉钉,乌鸫,饿了么,美团,三快,猫眼,快手,映客,小红书,行吟,奇虎,360,三六零,鸿盈,奇富,奇元,亚信,启明星辰,奇安信,深信服,长亭,绿盟,天融信,商汤,SenseTime,大华,海康威视,hikvision,汽车之家,车好多,瓜子,易车,昆仑万维,昆仑天工,闲徕,趣加,FunPlus,完美,马上消费,轻松,水滴,白龙马,58,更赢,车欢欢,五八,红布林,致美,快狗,天鹅到家,转转,美餐,知乎,智者四海,易点云,搜狐,用友,畅捷通,猿辅导,小猿,猿力,好未来,学而思,希望学,新东方,东方甄选,东方优选,作业帮,高途,跟谁学,学科网,天学网,一起教育,一起作业,美术宝,火花思维,粉笔,51talk,爱学习,高思,老虎国际,一心向上,向上一意,联想,拉勾,乐视,欢聚,竞技世界,拼多多,寻梦,从鲸,TEMU,得物,有赞,Moka,希瑞亚斯,北森,OPPO,欧珀,vivo,维沃,小天才,步步高,读书郎,货拉拉,陌陌,探探,Shopee,虾皮,首汽租车,GoFun,神州租车,天眼查,旷视,小冰,美图,智谱华章,MiniMax,石头科技,迅雷,TP,锐捷,Tenda,腾达,斐讯,希音,SHEIN,稀宇,深言,百川智能,与爱为舞,牵手,Grab,爱回收,洋钱罐,瓴岳,得到,思维造物,地平线,咪咕,翼支付,电信,天翼,联通,蓝湖,墨刀,海尔,美的,米哈游,传音,同花顺,国美,TCL`
- },
- {
- name: '阿里系',
- value: `阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,飞猪,乌鸫,饿了么,LAZADA,来赞达,菜鸟,哈啰,钉钉,高德,白龙马,新浪,微博`
- },
- {
- name: '字节(头条/抖音)系',
- value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,巨量引擎`
- },
- {
- name: '百度系',
- value: `百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,作业帮,智图,长地万方,瑞图万方,道道通,小熊博望`
- },
- {
- name: '腾讯系',
- value: `腾讯,酷狗,酷我,阅文,搜狗,小鹅通,富途,京东,沃东天骏,达达,达冠,京邦达,美团,三快,猫眼,快手,拼多多,寻梦,从鲸,TEMU,Shopee,虾皮,滴滴,嘀嘀,小桔,转转`
- },
- {
- name: '外包、劳务派遣企业',
- value: `青钱,软通动力,南天,睿服,中电金信,佰钧成,云链,博彦,汉克时代,柯莱特,拓保,亿达信息,纬创,微创,微澜,诚迈科技,法本,兆尹,诚迈,联合永道,新致软件,宇信科技,华为,德科,FESCO,科锐,科之锐`
- }
-]
-function handleExpectCompanyTemplateClicked(item) {
- gtagRenderer('expect_company_tpl_clicked', {
- name: item.name
- })
- formContent.value.expectCompanies = item.value
-}
-
-function handleExpectJobFilterTemplateClicked(item) {
- gtagRenderer('expect_job_filter_tpl_clicked', {
- name: item.name
- })
-
- Object.assign(formContent.value, {
- ...item.config
- })
-}
+const handleExpectJobFilterTemplateClicked = getHandlerForExpectJobFilterTemplateClicked({
+ gtagRenderer,
+ formContent
+})
const strategyOptionWhenCurrentJobNotMatch = [
{
@@ -1814,79 +2216,35 @@ const strategyScopeOptionWhenMarkJobNotMatch = [
}
]
-const jobDetailRegExpMatchLogicOptions = [
- {
- name: '“且”模式 - 所有正则匹配时才认为职位匹配',
- value: JobDetailRegExpMatchLogic.EVERY
- },
- {
- name: '“或”模式 - 任一正则匹配时即认为职位匹配',
- value: JobDetailRegExpMatchLogic.SOME
- }
-]
+const handleExpectSalaryCalculateWayChanged = getHandlerForExpectSalaryCalculateWayChanged({
+ gtagRenderer,
+ formContent
+})
-async function handleExpectSalaryCalculateWayChanged(value) {
- gtagRenderer('expect_salary_calculate_way_changed', { value })
-
- await nextTick()
- // convert annual package to month salary as 12-month
- if (value === SalaryCalculateWay.MONTH_SALARY) {
- if (formContent.value.expectSalaryHigh) {
- formContent.value.expectSalaryHigh = Number(
- ((formContent.value.expectSalaryHigh * 10) / 12).toFixed(2)
- )
- }
- if (formContent.value.expectSalaryLow) {
- formContent.value.expectSalaryLow = Number(
- ((formContent.value.expectSalaryLow * 10) / 12).toFixed(2)
- )
- }
- return
- }
- // convert month salary to annual package as 12-month
- else if (value === SalaryCalculateWay.ANNUAL_PACKAGE) {
- if (formContent.value.expectSalaryHigh) {
- formContent.value.expectSalaryHigh = Number(
- ((formContent.value.expectSalaryHigh / 10) * 12).toFixed(2)
- )
- }
- if (formContent.value.expectSalaryLow) {
- formContent.value.expectSalaryLow = Number(
- ((formContent.value.expectSalaryLow / 10) * 12).toFixed(2)
- )
- }
- return
- }
-}
-
-const expectSalaryCalculateWayOption = [
- {
- name: '月薪(单位为 千元 - 即“k”)',
- value: SalaryCalculateWay.MONTH_SALARY
- },
- {
- name: '“年包”(单位为 万元 - 即“W”)',
- value: SalaryCalculateWay.ANNUAL_PACKAGE
- }
-]
const salaryMarkAsNotSuitLabelText = computed(() => {
const textSeg = []
- if (formContent.value.expectSalaryLow) {
+ const formContentToUse = !formContent.value.fieldsForUseCommonConfig.salary
+ ? formContent.value
+ : commonJobConditionConfig.value
+ if (formContentToUse.expectSalaryLow) {
textSeg.push('低于期望薪资下限')
}
- if (formContent.value.expectSalaryHigh) {
+ if (formContentToUse.expectSalaryHigh) {
textSeg.push('高于期望薪资上限')
}
return textSeg.join(' / ')
})
const isShowSalaryMarkAsNotSuitStrategy = computed(() => {
- let flag = formContent.value.expectSalaryHigh || formContent.value.expectSalaryLow
+ const formContentToUse = !formContent.value.fieldsForUseCommonConfig.salary
+ ? formContent.value
+ : commonJobConditionConfig.value
+ let flag = formContentToUse.expectSalaryHigh || formContentToUse.expectSalaryLow
if (
- formContent.value.expectSalaryHigh &&
- formContent.value.expectSalaryLow &&
- formContent.value.expectSalaryHigh < formContent.value.expectSalaryLow
+ formContentToUse.expectSalaryHigh &&
+ formContentToUse.expectSalaryLow &&
+ formContentToUse.expectSalaryHigh < formContentToUse.expectSalaryLow
) {
flag = false
}
@@ -1894,30 +2252,6 @@ const isShowSalaryMarkAsNotSuitStrategy = computed(() => {
return flag
})
-function ensureSalaryRangeCorrect() {
- if (
- !formContent.value.expectSalaryHigh ||
- isNaN(parseFloat(formContent.value.expectSalaryHigh))
- ) {
- formContent.value.expectSalaryHigh = null
- } else {
- formContent.value.expectSalaryHigh = parseFloat(formContent.value.expectSalaryHigh.toFixed(2))
- }
- if (!formContent.value.expectSalaryLow || isNaN(parseFloat(formContent.value.expectSalaryLow))) {
- formContent.value.expectSalaryLow = null
- } else {
- formContent.value.expectSalaryLow = parseFloat(formContent.value.expectSalaryLow.toFixed(2))
- }
-
- if (
- formContent.value.expectSalaryLow &&
- formContent.value.expectSalaryHigh &&
- formContent.value.expectSalaryLow > formContent.value.expectSalaryHigh
- ) {
- formContent.value.expectSalaryHigh = formContent.value.expectSalaryLow
- }
-}
-
const noActiveDefinitionMarks = computed(() => {
let arr = [...activeDescList]
arr.shift()
@@ -1948,40 +2282,6 @@ function handleHowToFillDetailFilterClick() {
)
}
-function isJobDetailRegExpEmpty() {
- return [
- formContent.value.expectJobDescRegExpStr,
- formContent.value.expectJobNameRegExpStr,
- formContent.value.expectJobTypeRegExpStr
- ]
- .map((it) => Boolean(it?.trim()))
- .every((it) => it === false)
-}
-
-function getJobDetailRegExpMatchLogicConfig() {
- const result = {
- logicText: '-',
- inputPlaceholderText: '-'
- }
- if (formContent.value.jobDetailRegExpMatchLogic === JobDetailRegExpMatchLogic.EVERY) {
- Object.assign(result, {
- logicText: '且',
- inputPlaceholderText: 'true'
- })
- }
- if (formContent.value.jobDetailRegExpMatchLogic === JobDetailRegExpMatchLogic.SOME) {
- Object.assign(result, {
- logicText: '或',
- inputPlaceholderText: 'false'
- })
- }
-
- if (isJobDetailRegExpEmpty()) {
- result.inputPlaceholderText = 'true'
- }
- return result
-}
-
function formatJobSourceConfigToFormValue(config = []) {
const typeToNameKey = {
recommend: '推荐列表中的职位',
@@ -2063,25 +2363,75 @@ const handleStopButtonClick = async () => {
}
}
-const blockCompanyNameRegExpTemplateList = [
- {
- name: '不限公司(不按照公司名称来标注不合适)',
- value: ''
- },
- {
- name: '外包、劳务派遣企业',
- value: `青钱|软通动力|南天|睿服|中电金信|佰钧成|云链|博彦|汉克时代|柯莱特|拓保|亿达信息|纬创|微创|微澜|诚迈科技|法本|兆尹|诚迈|联合永道|新致软件|宇信科技|华为|德科|FESCO|科锐|科之锐`
- },
- {
- name: '京东及相关公司',
- value: '京东|沃东天骏|达达|达冠|京邦达'
- }
-]
-const handleBlockCompanyNameRegExpTemplateClicked = (item) => {
- gtagRenderer('bcn_reg_exp_tpl_clicked', {
- name: item.name
+const handleBlockCompanyNameRegExpTemplateClicked =
+ getHandlerForBlockCompanyNameRegExpTemplateClicked({
+ gtagRenderer,
+ formContent
})
- formContent.value.blockCompanyNameRegExpStr = item.value
+
+const commonJobConditionConfig = ref({})
+const unListenCommonJobConditionConfig = ipcRenderer.on(
+ 'common-job-condition-config-updated',
+ (_, { config }) => {
+ commonJobConditionConfig.value = {
+ ...config,
+ expectCompanies: config?.expectCompanies?.map((it) => it.trim())?.join(',') ?? ''
+ }
+ }
+)
+onUnmounted(() => {
+ unListenCommonJobConditionConfig()
+})
+
+const handleClickConfigCommonJobCondition = async ({ entry }) => {
+ gtagRenderer('config_cjc_clicked', { entry })
+ try {
+ await electron.ipcRenderer.invoke('common-job-condition-config')
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const fillCommonConfigField = (field) => {
+ gtagRenderer('fill_common_config_field_clicked', { field })
+ let fieldsToReplace = []
+ switch (field) {
+ case 'salary': {
+ fieldsToReplace = ['expectSalaryCalculateWay', 'expectSalaryLow', 'expectSalaryHigh']
+ break
+ }
+ case 'city': {
+ fieldsToReplace = ['expectCityList']
+ break
+ }
+ case 'jobDetail': {
+ fieldsToReplace = [
+ 'jobDetailRegExpMatchLogic',
+ 'expectJobNameRegExpStr',
+ 'expectJobTypeRegExpStr',
+ 'expectJobDescRegExpStr'
+ ]
+ break
+ }
+ case 'expectCompanies': {
+ fieldsToReplace = ['expectCompanies']
+ break
+ }
+ case 'blockCompanyNameRegExpStr': {
+ fieldsToReplace = ['blockCompanyNameRegExpStr']
+ break
+ }
+ }
+ for (const field of fieldsToReplace) {
+ let sourceValue = commonJobConditionConfig.value[field]
+ if (
+ commonJobConditionConfig.value[field] &&
+ typeof commonJobConditionConfig.value[field] === 'object'
+ ) {
+ sourceValue = JSON.parse(JSON.stringify(commonJobConditionConfig.value[field]))
+ }
+ formContent.value[field] = sourceValue
+ }
}
diff --git a/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/BossPart.vue b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/BossPart.vue
new file mode 100644
index 0000000..39fb6b1
--- /dev/null
+++ b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/BossPart.vue
@@ -0,0 +1,128 @@
+
+
+
逛BOSS
+
+
+ 自动开聊
+
+
+
+
扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。
+
+
匹配步骤
+
+ -
+ 按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
+
+ -
+ 检查BOSS活跃度
+
+ -
+ 如果BOSS活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
+
+
+
+ -
+ 对职位名称、职位类型、职位描述进行匹配
+
+ - 如果匹配则自动点击开聊按钮
+ -
+ 不匹配则标记这个职位为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
+
+
+
+
+
+
异常情况
+
+ -
+ 当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
+
+ -
+ 如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
+
+
+
+
+
+
+
+
+ 已读不回自动复聊
+
+
+
+
+ BOSS不明原因已读不回?简历就是投不出去?
+ 已读不回自动复聊,提醒一下已读不回的 BOSS,助力把握每次机会
+
+
+
匹配逻辑
+
在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时:
+
+ - 如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查
+ -
+ 如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间
+
+
+
+
发送内容
+
+ - “[盼回复]”表情
+ - 由大语言模型(根据简历及当前聊天上下文)生成的内容
+
+
+
+
+
+
+
+ 编辑登录凭据
+
+
+ 手动逛
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/GlabalConfigPart.vue b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/GlabalConfigPart.vue
new file mode 100644
index 0000000..8782032
--- /dev/null
+++ b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/GlabalConfigPart.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/RunDataRecordPart.vue b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/RunDataRecordPart.vue
new file mode 100644
index 0000000..2e670e1
--- /dev/null
+++ b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/RunDataRecordPart.vue
@@ -0,0 +1,14 @@
+
+
+
运行数据
+
+ 开聊记录
+ 标记不合适记录
+ 职位库
+ BOSS库
+ 公司库
+
+
+
+
+
diff --git a/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/style.scss b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/style.scss
new file mode 100644
index 0000000..7ef8989
--- /dev/null
+++ b/packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/style.scss
@@ -0,0 +1,26 @@
+.group-item {
+ .group-title {
+ color: #849492;
+ font-size: 12px;
+ padding: 0.25em 0;
+ }
+ .link-list {
+ a {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 2em;
+ box-sizing: border-box;
+ padding-left: 1em;
+ font-size: 14px;
+ &.router-link-active {
+ background-color: #fff;
+ font-weight: 700;
+ color: #2faa9e;
+ border-radius: 9999px 0 0 9999px;
+ position: relative;
+ box-shadow: 0px 0px 10px rgba(50, 114, 108, 0.187);
+ }
+ }
+ }
+}
diff --git a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue
index 4da0765..cf48a23 100644
--- a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue
+++ b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue
@@ -9,7 +9,11 @@
>
-
+
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
@@ -17,7 +21,7 @@
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
-
当前职位类型正则:{{ expectJobTypeRegExpStr?.trim() }}
+
当前职位类型正则:{{ expectJobTypeRegExpStrForRender?.trim() }}
+
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
@@ -41,7 +49,7 @@
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
-
当前不期望投递公司正则:{{ blockCompanyNameRegExpStr?.trim() }}
+
当前不期望投递公司正则:{{ blockCompanyNameRegExpStrForRender?.trim() }}
@@ -313,10 +321,13 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
const expectJobTypeRegExpStr = ref('')
const blockCompanyNameRegExpStr = ref('')
+const fieldsForUseCommonConfig = ref({})
async function fetchAutoStartChatConfig() {
await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr
blockCompanyNameRegExpStr.value = res.config['boss.json']?.blockCompanyNameRegExpStr
+ fieldsForUseCommonConfig.value = res.config['boss.json']?.fieldsForUseCommonConfig ?? {}
+ commonJobConditionConfig.value = res.config['common-job-condition-config.json']
})
}
fetchAutoStartChatConfig()
@@ -324,6 +335,30 @@ mittBus.on('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig)
onUnmounted(() => {
mittBus.off('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig)
})
+const commonJobConditionConfig = ref({})
+const unListenCommonJobConditionConfig = electron.ipcRenderer.on(
+ 'common-job-condition-config-updated',
+ (_, { config }) => {
+ commonJobConditionConfig.value = {
+ ...config,
+ expectCompanies: config?.expectCompanies?.map((it) => it.trim())?.join(',') ?? ''
+ }
+ }
+)
+onUnmounted(() => {
+ unListenCommonJobConditionConfig()
+})
+
+const expectJobTypeRegExpStrForRender = computed(() => {
+ return !fieldsForUseCommonConfig.value.jobDetail
+ ? expectJobTypeRegExpStr.value
+ : commonJobConditionConfig.value.expectJobTypeRegExpStr
+})
+const blockCompanyNameRegExpStrForRender = computed(() => {
+ return !fieldsForUseCommonConfig.value.blockCompanyNameRegExpStr
+ ? blockCompanyNameRegExpStr.value
+ : commonJobConditionConfig.value.blockCompanyNameRegExpStr
+})
const resumeContent = ref(null)
async function fetchResumeContent() {
diff --git a/packages/ui/src/renderer/src/page/MainLayout/index.vue b/packages/ui/src/renderer/src/page/MainLayout/index.vue
index 7a8a0ed..65bf462 100644
--- a/packages/ui/src/renderer/src/page/MainLayout/index.vue
+++ b/packages/ui/src/renderer/src/page/MainLayout/index.vue
@@ -3,153 +3,11 @@
任务管理
-
-
BOSS直聘
-
-
- 自动开聊
-
-
-
-
扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。
-
-
匹配步骤
-
- -
- 按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
-
- -
- 检查BOSS活跃度
-
- -
- 如果BOSS活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
-
-
-
- -
- 对职位名称、职位类型、职位描述进行匹配
-
- - 如果匹配则自动点击开聊按钮
- -
- 不匹配则标记这个职位为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
-
-
-
-
-
-
异常情况
-
- -
- 当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
-
- -
- 如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
-
-
-
-
-
-
-
-
- 已读不回自动复聊
-
-
-
-
- BOSS不明原因已读不回?简历就是投不出去?
- 已读不回自动复聊,提醒一下已读不回的 BOSS,助力把握每次机会
-
-
-
匹配逻辑
-
在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时:
-
- - 如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查
- -
- 如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间
-
-
-
-
发送内容
-
- - “[盼回复]”表情
- - 由大语言模型(根据简历及当前聊天上下文)生成的内容
-
-
-
-
-
-
-
- 编辑登录凭据
-
-
- 手动逛
-
-
-
+
-
+
-
-
运行数据
-
- 开聊记录
- 标记不合适记录
- 职位库
- BOSS库
- 公司库
-
-
+