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 @@ + + + + + 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' }" > - +
- +
+ 使用在“公共职位筛选条件”中设置的值 + 编辑公共职位筛选条件 + 填入公共职位筛选条件的值 +
+ + + +
- -
-
薪资筛选方式
- - {{ op.name }} + 使用在“公共职位筛选条件”中设置的值 + 编辑公共职位筛选条件 + 填入公共职位筛选条件的值 +
+ +
- + 职位详情筛选模板(按职类区分)
- +
+ 使用在“公共职位筛选条件”中设置的值 + 编辑公共职位筛选条件 + 填入公共职位筛选条件的值 +
+
职位名称/类型/描述 正则匹配筛选逻辑
+ +
职位名称/类型/描述 正则匹配筛选逻辑
+ + {{ 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 @@ + + + + + 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 @@ + + + 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沟通的岗位是否满足期望,校验通过后再提醒