mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-07 00:17:17 +08:00
Merge branch 'feature/support-other-platform-and-add-global-setting' into feature/ui
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
# 牛人快跑 - GeekGeekRun
|
# 牛人快跑 - GeekGeekRun
|
||||||
|
|
||||||
**->维护者正在疯狂求职中<-**
|
|
||||||
|
|
||||||
一款可以帮助你在BOSS直聘上**自动批量开聊BOSS**的脚本,基于Puppeteer。
|
一款可以帮助你在BOSS直聘上**自动批量开聊BOSS**的脚本,基于Puppeteer。
|
||||||
|
|
||||||
与每一位牛人站在一起
|
与每一位牛人站在一起
|
||||||
@@ -11,8 +9,8 @@
|
|||||||
|
|
||||||
各行各业,无论你是小白还是大佬,都能通过几步简单的配置,快速开始求职!
|
各行各业,无论你是小白还是大佬,都能通过几步简单的配置,快速开始求职!
|
||||||
|
|
||||||
## TO 有求职之外其他目的朋友
|
## TO 有求职之外其他用途的朋友
|
||||||
本程序的目标,是帮助求职者尽快寻得一个能让他为之**挥洒汗水**、**努力拼搏**的**事业**。如果有其它目的,请勿下载使用。
|
本程序的目标,是帮助求职者调研求职市场行情或寻找工作。如果有其它用途,请勿下载使用。
|
||||||
|
|
||||||
## 程序有哪些功能?运行逻辑是什么?怎样使用?
|
## 程序有哪些功能?运行逻辑是什么?怎样使用?
|
||||||
|
|
||||||
@@ -152,8 +150,8 @@ BOSS不明原因已读不回?简历就是投不出去?
|
|||||||
- 本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,**不会泄露给第三方**,也不会进行除自动开聊BOSS以外的行为;**请勿向他人泄漏您的Cookie**。
|
- 本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,**不会泄露给第三方**,也不会进行除自动开聊BOSS以外的行为;**请勿向他人泄漏您的Cookie**。
|
||||||
- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的BOSS直聘账号**进行求职。
|
- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的BOSS直聘账号**进行求职。
|
||||||
- 本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
|
- 本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
|
||||||
- 您所在公司可能会对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;上级/HRBP团队可能会从 IT 团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级/HRBP团队了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
|
- 您所在公司可能会采购上网行为监控工具或网关(例如奇安信、深信服、绿盟等厂商的产品),对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;您的上级/IT/HR 可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR 了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
|
||||||
- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及上级/HRBP报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。
|
- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及您的上级/IT/HR 报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。但由于本程序开源,任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序。
|
||||||
- 本程序**没有内置任何付费功能**,**下载**、**使用**是**免费**的,任何人可以**免费获得**、**免费使用**。**作者没有利用本程序赚到过任何收入**。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,那**您大概率被骗了**,或者**您下载到了本程序修改版**。**本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方**。
|
- 本程序**没有内置任何付费功能**,**下载**、**使用**是**免费**的,任何人可以**免费获得**、**免费使用**。**作者没有利用本程序赚到过任何收入**。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,那**您大概率被骗了**,或者**您下载到了本程序修改版**。**本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方**。
|
||||||
- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。
|
- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。
|
||||||
- 请在BOSS直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请**在BOSS直聘上屏蔽当前公司及与之关联的公司**。
|
- 请在BOSS直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请**在BOSS直聘上屏蔽当前公司及与之关联的公司**。
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
"staticCombineRecommendJobFilterConditions": [],
|
"staticCombineRecommendJobFilterConditions": [],
|
||||||
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
|
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
|
||||||
"expectJobRegExpStr": "",
|
"expectJobRegExpStr": "",
|
||||||
|
"expectJobNameRegExpStr": "",
|
||||||
|
"expectJobTypeRegExpStr": "",
|
||||||
|
"expectJobDescRegExpStr": "",
|
||||||
"jobNotMatchStrategy": 1,
|
"jobNotMatchStrategy": 1,
|
||||||
"jobNotActiveStrategy": 1,
|
"jobNotActiveStrategy": 1,
|
||||||
"markAsNotActiveSelectedTimeRange": 7,
|
"markAsNotActiveSelectedTimeRange": 7,
|
||||||
@@ -36,5 +39,6 @@
|
|||||||
"isSageTimeEnabled": true,
|
"isSageTimeEnabled": true,
|
||||||
"sageTimeOpTimes": 100,
|
"sageTimeOpTimes": 100,
|
||||||
"sageTimePauseMinute": 15,
|
"sageTimePauseMinute": 15,
|
||||||
"blockCompanyNameRegExpStr": ""
|
"blockCompanyNameRegExpStr": "",
|
||||||
|
"fieldsForUseCommonConfig": {}
|
||||||
}
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"expectCityList": [],
|
||||||
|
"expectJobNameRegExpStr": "",
|
||||||
|
"expectJobTypeRegExpStr": "",
|
||||||
|
"expectJobDescRegExpStr": "",
|
||||||
|
"expectCompanies": [],
|
||||||
|
"blockCompanyNameRegExpStr": "",
|
||||||
|
"jobDetailRegExpMatchLogic": 1,
|
||||||
|
"expectSalaryCalculateWay": 1,
|
||||||
|
"expectSalaryLow": null,
|
||||||
|
"expectSalaryHigh": null
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
|
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
|
||||||
import { waitForSageTimeOrJustContinue } from './sage-time.mjs'
|
import { waitForSageTimeOrJustContinue } from './sage-time.mjs'
|
||||||
import cityGroupData from './cityGroup.mjs'
|
import cityGroupData from './cityGroup.mjs'
|
||||||
|
import { hasIntersection } from '@geekgeekrun/utils/number.mjs';
|
||||||
const flattedCityList = []
|
const flattedCityList = []
|
||||||
;(cityGroupData?.zpData?.cityGroup ?? []).forEach(it => {
|
;(cityGroupData?.zpData?.cityGroup ?? []).forEach(it => {
|
||||||
const firstChar = it.firstChar
|
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 combineRecommendJobFilterType = readConfigFile('boss.json').combineRecommendJobFilterType ?? CombineRecommendJobFilterType.ANY_COMBINE
|
||||||
|
|
||||||
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
|
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 jobNotMatchStrategy = readConfigFile('boss.json').jobNotMatchStrategy ?? MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||||
|
|
||||||
const expectCityNotMatchStrategy = readConfigFile('boss.json').expectCityNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
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
|
const strategyScopeOptionWhenMarkJobCityNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobCityNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||||
|
|
||||||
// salary
|
// salary
|
||||||
const expectSalaryLow = parseFloat(readConfigFile('boss.json').expectSalaryLow) || null
|
const expectSalaryLow = parseFloat(
|
||||||
const expectSalaryHigh = parseFloat(readConfigFile('boss.json').expectSalaryHigh) || null
|
!fieldsForUseCommonConfig.salary ?
|
||||||
const expectSalaryCalculateWay = readConfigFile('boss.json').expectSalaryCalculateWay ?? SalaryCalculateWay.MONTH_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 expectSalaryNotMatchStrategy = readConfigFile('boss.json').expectSalaryNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||||
const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
|
const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
|
||||||
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||||
@@ -153,7 +182,12 @@ expectWorkExpList = Array.from(expectWorkExpListSet)
|
|||||||
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||||
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||||
|
|
||||||
let jobDetailRegExpMatchLogic = readConfigFile('boss.json').jobDetailRegExpMatchLogic ?? JobDetailRegExpMatchLogic.EVERY
|
let jobDetailRegExpMatchLogic = (
|
||||||
|
!fieldsForUseCommonConfig.jobDetail ?
|
||||||
|
readConfigFile('boss.json').jobDetailRegExpMatchLogic
|
||||||
|
:
|
||||||
|
commonJobConditionConfig.jobDetailRegExpMatchLogic
|
||||||
|
) ?? JobDetailRegExpMatchLogic.EVERY
|
||||||
|
|
||||||
const markAsNotActiveSelectedTimeRange = (() => {
|
const markAsNotActiveSelectedTimeRange = (() => {
|
||||||
let n = readConfigFile('boss.json').markAsNotActiveSelectedTimeRange
|
let n = readConfigFile('boss.json').markAsNotActiveSelectedTimeRange
|
||||||
@@ -176,8 +210,9 @@ let {
|
|||||||
expectJobNameRegExpStr,
|
expectJobNameRegExpStr,
|
||||||
expectJobTypeRegExpStr,
|
expectJobTypeRegExpStr,
|
||||||
expectJobDescRegExpStr,
|
expectJobDescRegExpStr,
|
||||||
} = readConfigFile('boss.json')
|
} = !fieldsForUseCommonConfig.jobDetail ? readConfigFile('boss.json') : commonJobConditionConfig
|
||||||
if (
|
if (
|
||||||
|
!fieldsForUseCommonConfig.jobDetail &&
|
||||||
expectJobRegExpStr &&
|
expectJobRegExpStr &&
|
||||||
!expectJobNameRegExpStr &&
|
!expectJobNameRegExpStr &&
|
||||||
!expectJobTypeRegExpStr &&
|
!expectJobTypeRegExpStr &&
|
||||||
@@ -247,7 +282,12 @@ const recommendJobPageUrl = `https://www.zhipin.com/web/geek/jobs`
|
|||||||
|
|
||||||
const expectCompanySet = new Set(targetCompanyList)
|
const expectCompanySet = new Set(targetCompanyList)
|
||||||
const enableCompanyAllowList = Boolean(expectCompanySet.size)
|
const enableCompanyAllowList = Boolean(expectCompanySet.size)
|
||||||
const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? ''
|
const blockCompanyNameRegExpStr = (
|
||||||
|
!fieldsForUseCommonConfig.blockCompanyNameRegExpStr ?
|
||||||
|
readConfigFile('boss.json').blockCompanyNameRegExpStr
|
||||||
|
:
|
||||||
|
commonJobConditionConfig.blockCompanyNameRegExpStr
|
||||||
|
) ?? ''
|
||||||
const blockCompanyNameRegExp = (() => {
|
const blockCompanyNameRegExp = (() => {
|
||||||
if (!blockCompanyNameRegExpStr?.trim()) {
|
if (!blockCompanyNameRegExpStr?.trim()) {
|
||||||
return null
|
return null
|
||||||
@@ -947,20 +987,24 @@ async function toRecommendPage (hooks) {
|
|||||||
function checkIfSalarySuit(salaryDesc) {
|
function checkIfSalarySuit(salaryDesc) {
|
||||||
const salaryData = parseSalary(salaryDesc)
|
const salaryData = parseSalary(salaryDesc)
|
||||||
if (expectSalaryCalculateWay === SalaryCalculateWay.MONTH_SALARY) {
|
if (expectSalaryCalculateWay === SalaryCalculateWay.MONTH_SALARY) {
|
||||||
if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
|
let ourSalaryInterval = [expectSalaryLow ?? null, expectSalaryHigh ?? null]
|
||||||
return false
|
if (ourSalaryInterval.every(it => !isNaN(parseFloat(it)))) {
|
||||||
|
ourSalaryInterval = ourSalaryInterval.sort((a, b) => a - b)
|
||||||
}
|
}
|
||||||
if (expectSalaryLow && salaryData.high < expectSalaryLow) {
|
const theirSalaryInterval = [salaryData.low ?? null, salaryData.high ?? null]
|
||||||
return false
|
return hasIntersection(theirSalaryInterval, ourSalaryInterval)
|
||||||
}
|
}
|
||||||
} else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||||
const salaryDataMonth = salaryData.month || 12
|
const salaryDataMonth = salaryData.month || 12
|
||||||
if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
|
let ourSalaryInterval = [expectSalaryLow ?? null, expectSalaryHigh ?? null]
|
||||||
return false
|
if (ourSalaryInterval.every(it => !isNaN(parseFloat(it)))) {
|
||||||
}
|
ourSalaryInterval = ourSalaryInterval.sort((a, b) => a - b)
|
||||||
if (expectSalaryLow && (salaryData.high * salaryDataMonth) / 10 < expectSalaryLow) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
const theirSalaryInterval = [salaryData.low ?? null, salaryData.high ?? null].map(
|
||||||
|
it =>
|
||||||
|
it === null ? null : (it * salaryDataMonth / 10)
|
||||||
|
)
|
||||||
|
return hasIntersection(theirSalaryInterval, ourSalaryInterval)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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 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' }
|
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 = {
|
const defaultConfigFileContentMap = {
|
||||||
'boss.json': JSON.stringify(defaultBossConf),
|
'boss.json': JSON.stringify(defaultBossConf),
|
||||||
'dingtalk.json': JSON.stringify(defaultDingtalkConf),
|
'dingtalk.json': JSON.stringify(defaultDingtalkConf),
|
||||||
'target-company-list.json': JSON.stringify(defaultTargetCompanyListConf),
|
'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 = () => {
|
const ensureRuntimeFolderPathExist = () => {
|
||||||
if (!fs.existsSync(runtimeFolderPath)) {
|
if (!fs.existsSync(runtimeFolderPath)) {
|
||||||
fs.mkdirSync(runtimeFolderPath)
|
fs.mkdirSync(runtimeFolderPath)
|
||||||
@@ -35,11 +133,6 @@ const ensureRuntimeFolderPathExist = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configFolderPath = path.join(
|
|
||||||
runtimeFolderPath,
|
|
||||||
'config'
|
|
||||||
)
|
|
||||||
export const ensureConfigFileExist = () => {
|
export const ensureConfigFileExist = () => {
|
||||||
ensureRuntimeFolderPathExist()
|
ensureRuntimeFolderPathExist()
|
||||||
;configFileNameList.forEach(
|
;configFileNameList.forEach(
|
||||||
@@ -82,15 +175,6 @@ export const readConfigFile = (fileName) => {
|
|||||||
return o
|
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(
|
export const storageFilePath = path.join(
|
||||||
runtimeFolderPath,
|
runtimeFolderPath,
|
||||||
'storage'
|
'storage'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "geekgeekrun-ui",
|
"name": "geekgeekrun-ui",
|
||||||
"version": "0.15.0",
|
"version": "0.15.2",
|
||||||
"description": "BOSS 炸弹 - 自动开聊BOSS,助力每位打工人求职!",
|
"description": "BOSS 炸弹 - 自动开聊BOSS,助力每位打工人求职!",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "geekgeekrun",
|
"author": "geekgeekrun",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "0.15.0",
|
"version": "0.15.2",
|
||||||
"buildVersion": 33,
|
"buildVersion": 35,
|
||||||
"buildTime": 1770822824486,
|
"buildTime": 1770854432774,
|
||||||
"buildHash": "333b9a4558f753a7734bb8972b620d916b55418b",
|
"buildHash": "81f990084df491d8dfd8ddb68b3895d5a4019fe9",
|
||||||
"name": "geekgeekrun-ui"
|
"name": "geekgeekrun-ui"
|
||||||
}
|
}
|
||||||
@@ -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'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,8 +2,6 @@ import { ipcMain, shell, app, dialog, BrowserWindow } from 'electron'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import * as childProcess from 'node:child_process'
|
import * as childProcess from 'node:child_process'
|
||||||
import {
|
import {
|
||||||
ensureConfigFileExist,
|
|
||||||
configFileNameList,
|
|
||||||
readConfigFile,
|
readConfigFile,
|
||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
readStorageFile,
|
readStorageFile,
|
||||||
@@ -58,23 +56,10 @@ import {
|
|||||||
waitForUserApproveAgreement
|
waitForUserApproveAgreement
|
||||||
} from '../../../features/first-launch-notice-window'
|
} from '../../../features/first-launch-notice-window'
|
||||||
import { getLastUsedAndAvailableBrowser } from '../../DOWNLOAD_DEPENDENCIES/utils/browser-history'
|
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() {
|
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) => {
|
ipcMain.handle('save-config-file-from-ui', async (ev, payload) => {
|
||||||
payload = JSON.parse(payload)
|
payload = JSON.parse(payload)
|
||||||
ensureConfigFileExist()
|
ensureConfigFileExist()
|
||||||
@@ -187,6 +172,9 @@ export default function initIpc() {
|
|||||||
if (hasOwn(payload, 'blockCompanyNameRegMatchStrategy')) {
|
if (hasOwn(payload, 'blockCompanyNameRegMatchStrategy')) {
|
||||||
bossConfig.blockCompanyNameRegMatchStrategy = payload.blockCompanyNameRegMatchStrategy
|
bossConfig.blockCompanyNameRegMatchStrategy = payload.blockCompanyNameRegMatchStrategy
|
||||||
}
|
}
|
||||||
|
if (hasOwn(payload, 'fieldsForUseCommonConfig')) {
|
||||||
|
bossConfig.fieldsForUseCommonConfig = payload.fieldsForUseCommonConfig
|
||||||
|
}
|
||||||
|
|
||||||
promiseArr.push(writeConfigFile('boss.json', bossConfig))
|
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', () => {
|
ipcMain.handle('exit-app-immediately', () => {
|
||||||
app.exit(0)
|
app.exit(0)
|
||||||
|
|||||||
@@ -58,12 +58,21 @@ const rechatLlmFallback =
|
|||||||
readConfigFile('boss.json').autoReminder?.rechatLlmFallback ??
|
readConfigFile('boss.json').autoReminder?.rechatLlmFallback ??
|
||||||
RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION
|
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 =
|
const onlyRemindBossWithExpectJobType =
|
||||||
readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ??
|
readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ??
|
||||||
!!expectJobTypeRegExpStr
|
!!expectJobTypeRegExpStr
|
||||||
|
|
||||||
const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? ''
|
const blockCompanyNameRegExpStr =
|
||||||
|
(!fieldsForUseCommonConfig.blockCompanyNameRegExpStr
|
||||||
|
? readConfigFile('boss.json')
|
||||||
|
: commonJobConditionConfig
|
||||||
|
)?.blockCompanyNameRegExpStr ?? ''
|
||||||
|
|
||||||
const blockCompanyNameRegExp = (() => {
|
const blockCompanyNameRegExp = (() => {
|
||||||
if (!blockCompanyNameRegExpStr?.trim()) {
|
if (!blockCompanyNameRegExpStr?.trim()) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import os from 'node:os'
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import {
|
import {
|
||||||
ensureStorageFileExist,
|
ensureStorageFileExist,
|
||||||
readStorageFile,
|
writeStorageFile,
|
||||||
writeStorageFile
|
configFileNameList,
|
||||||
|
readConfigFile,
|
||||||
|
readStorageFile
|
||||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||||
|
|
||||||
export default function initPublicIpc() {
|
export default function initPublicIpc() {
|
||||||
@@ -110,4 +112,19 @@ export default function initPublicIpc() {
|
|||||||
}
|
}
|
||||||
return null
|
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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!
|
||||||
|
}
|
||||||
@@ -0,0 +1,733 @@
|
|||||||
|
<template>
|
||||||
|
<div class="common-job-condition-config" flex flex-col h-full>
|
||||||
|
<div flex-1 of-auto>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formContent"
|
||||||
|
:rules="formRules"
|
||||||
|
inline-message
|
||||||
|
w-800px
|
||||||
|
pt-30px
|
||||||
|
pb-30px
|
||||||
|
ml-auto
|
||||||
|
mr-auto
|
||||||
|
>
|
||||||
|
<div mb20px>公共职位筛选条件</div>
|
||||||
|
<el-form-item prop="expectCompanies" mb0>
|
||||||
|
<div
|
||||||
|
font-size-14px
|
||||||
|
flex
|
||||||
|
mb6px
|
||||||
|
:style="{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
width: '100%',
|
||||||
|
lineHeight: '1.25em'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
期望投递公司
|
||||||
|
<br /><span font-size-12px
|
||||||
|
><b color-orange>逗号分隔</b>,不区分大小写;输入框留空表示不筛选</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<el-dropdown @command="handleExpectCompanyTemplateClicked">
|
||||||
|
<el-button size="small"
|
||||||
|
>公司列表模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
|
></el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-for="item in expectCompanyTemplateList"
|
||||||
|
:key="item.name"
|
||||||
|
:command="item"
|
||||||
|
>{{ item.name }}</el-dropdown-item
|
||||||
|
>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="formContent.expectCompanies"
|
||||||
|
:autosize="{ minRows: 4 }"
|
||||||
|
max-h-8lh
|
||||||
|
type="textarea"
|
||||||
|
placeholder="置空表示“不限公司,任意公司都可以投递”"
|
||||||
|
@blur="
|
||||||
|
formContent.expectCompanies = normalizeCommaSplittedStr(formContent.expectCompanies)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<div class="h-1px bg-#f0f0f0" mt16px mb8px />
|
||||||
|
<div
|
||||||
|
ref="blockCompanyNameRegExpSectionEl"
|
||||||
|
font-size-14px
|
||||||
|
flex
|
||||||
|
:style="{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
width: '100%',
|
||||||
|
lineHeight: '1.25em'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div mb6px>
|
||||||
|
不期望投递公司<b color-orange>正则</b><br /><span font-size-12px
|
||||||
|
><b color-orange>正则表达式</b>,不区分大小写;输入框留空表示不筛选;<span
|
||||||
|
color-orange
|
||||||
|
>优先级高于上方“期望投递公司”</span
|
||||||
|
><br />请<b color-red>小心验证</b
|
||||||
|
>你编写的正则,填写太过于宽泛的正则(例如`.*`)将导致任何职位都不会开聊</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<el-dropdown @command="handleBlockCompanyNameRegExpTemplateClicked">
|
||||||
|
<el-button size="small"
|
||||||
|
>公司列表模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
|
></el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-for="item in blockCompanyNameRegExpTemplateList"
|
||||||
|
:key="item.name"
|
||||||
|
:command="item"
|
||||||
|
>{{ item.name }}</el-dropdown-item
|
||||||
|
>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="block-company-filter-wrap"
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: '10px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-form-item prop="blockCompanyNameRegExpStr" mb0 w-full>
|
||||||
|
<el-input
|
||||||
|
v-model="formContent.blockCompanyNameRegExpStr"
|
||||||
|
:autosize="{ minRows: 4 }"
|
||||||
|
max-h-8lh
|
||||||
|
type="textarea"
|
||||||
|
placeholder="置空表示“不限公司,任意公司都不会被标记为不合适”"
|
||||||
|
@blur="
|
||||||
|
formContent.blockCompanyNameRegExpStr =
|
||||||
|
formContent.blockCompanyNameRegExpStr?.trim() ?? ''
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="h-1px bg-#f0f0f0" mt16px mb16px />
|
||||||
|
<div mt16px>
|
||||||
|
<div font-size-14px mb8px>工作地</div>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: '10px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-form-item prop="expectCityList" mb0>
|
||||||
|
<div
|
||||||
|
font-size-12px
|
||||||
|
:style="{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<city-chooser v-model="formContent.expectCityList">
|
||||||
|
<template #default="{ modelValue, showDialog, clearValue }">
|
||||||
|
<div v-if="modelValue?.length">
|
||||||
|
<div>当前已选择城市:</div>
|
||||||
|
<div flex flex-wrap gap-10px>
|
||||||
|
<el-tag v-for="it in modelValue" :key="it">
|
||||||
|
{{ it }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div>当前未选择任何期望城市,将不会按照城市进行筛选</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
line-height-1
|
||||||
|
:style="{
|
||||||
|
marginTop: modelValue?.length ? '10px' : ''
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
// isDialogVisible = true
|
||||||
|
showDialog()
|
||||||
|
gtagRenderer('choose_city_entry_button_clicked')
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>选择城市</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="modelValue?.length"
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
@click="clearValue"
|
||||||
|
>清空已选择的所有城市</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</city-chooser>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-1px bg-#f0f0f0" mt16px mb16px />
|
||||||
|
<div mt16px>
|
||||||
|
<div font-size-14px mb8px>
|
||||||
|
薪资(仅支持按月计算薪资的职位;非按月计算薪资职位(例如兼职职位、实习职位)将直接跳过)
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: '10px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<el-form-item prop="expectSalaryLow" mb10px>
|
||||||
|
<div w-full>
|
||||||
|
<div font-size-12px>薪资筛选方式</div>
|
||||||
|
<el-select
|
||||||
|
v-model="formContent.expectSalaryCalculateWay"
|
||||||
|
@change="handleExpectSalaryCalculateWayChanged"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="op in expectSalaryCalculateWayOption"
|
||||||
|
:key="op.value"
|
||||||
|
:label="op.name"
|
||||||
|
:value="op.value"
|
||||||
|
>{{ op.name }}</el-option
|
||||||
|
>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="expectSalaryLow" mb10px>
|
||||||
|
<div>
|
||||||
|
<div font-size-12px>期望薪资范围</div>
|
||||||
|
<div>
|
||||||
|
<el-input-number
|
||||||
|
v-model="formContent.expectSalaryLow"
|
||||||
|
controls-position="right"
|
||||||
|
:min="0"
|
||||||
|
:step="0.25"
|
||||||
|
placeholder="不设置"
|
||||||
|
@change="
|
||||||
|
() => {
|
||||||
|
gtagRenderer('expect_salary_low_changed')
|
||||||
|
ensureSalaryRangeCorrect({ formContent })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #prefix>下限</template>
|
||||||
|
<template #suffix>
|
||||||
|
<template v-if="formContent.expectSalaryLow">
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
formContent.expectSalaryCalculateWay ===
|
||||||
|
SalaryCalculateWay.MONTH_SALARY
|
||||||
|
"
|
||||||
|
>k</template
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
formContent.expectSalaryCalculateWay ===
|
||||||
|
SalaryCalculateWay.ANNUAL_PACKAGE
|
||||||
|
"
|
||||||
|
>W</template
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-input-number>
|
||||||
|
-
|
||||||
|
<el-input-number
|
||||||
|
v-model="formContent.expectSalaryHigh"
|
||||||
|
controls-position="right"
|
||||||
|
:min="0"
|
||||||
|
:step="0.25"
|
||||||
|
placeholder="不设置"
|
||||||
|
@change="
|
||||||
|
() => {
|
||||||
|
gtagRenderer('expect_salary_high_changed')
|
||||||
|
ensureSalaryRangeCorrect({ formContent })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #prefix>上限</template>
|
||||||
|
<template #suffix>
|
||||||
|
<template v-if="formContent.expectSalaryHigh">
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
formContent.expectSalaryCalculateWay ===
|
||||||
|
SalaryCalculateWay.MONTH_SALARY
|
||||||
|
"
|
||||||
|
>k</template
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
formContent.expectSalaryCalculateWay ===
|
||||||
|
SalaryCalculateWay.ANNUAL_PACKAGE
|
||||||
|
"
|
||||||
|
>W</template
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-input-number>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="
|
||||||
|
formContent.expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE &&
|
||||||
|
(formContent.expectSalaryLow || formContent.expectSalaryHigh)
|
||||||
|
"
|
||||||
|
mb10px
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div font-size-12px>薪资范围满足以下条件的职位将会被匹配</div>
|
||||||
|
<div>
|
||||||
|
<div flex flex-nowrap flex-items-start>
|
||||||
|
<template
|
||||||
|
v-for="(mGroup, index) in [
|
||||||
|
[12, 13, 14, 15, 16, 17, 18],
|
||||||
|
[19, 20, 21, 22, 23, 24]
|
||||||
|
]"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
:style="{
|
||||||
|
lineHeight: '1.25em'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-for="(text, i) in ['月薪下限', '月薪上限', '']"
|
||||||
|
:key="i"
|
||||||
|
:style="{
|
||||||
|
borderBottom: '2px solid #f0f0f0'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="m in mGroup" :key="m">
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
formContent.expectSalaryLow
|
||||||
|
? ((formContent.expectSalaryLow / m) * 10).toFixed(2)
|
||||||
|
: '无下限'
|
||||||
|
}}<small v-if="formContent.expectSalaryLow" class="color-#999 ml-2px"
|
||||||
|
>k</small
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
formContent.expectSalaryHigh
|
||||||
|
? ((formContent.expectSalaryHigh / m) * 10).toFixed(2)
|
||||||
|
: '无上限'
|
||||||
|
}}<small v-if="formContent.expectSalaryHigh" class="color-#999 ml-2px"
|
||||||
|
>k</small
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>{{ m }}薪</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div v-if="index !== 1" class="bg-#f0f0f0 w-2px flex-self-stretch"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-1px bg-#f0f0f0" mt16px mb16px />
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
flex
|
||||||
|
:style="{
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div font-size-14px>期望职位信息</div>
|
||||||
|
<div>
|
||||||
|
<el-dropdown ml20px @command="handleExpectJobFilterTemplateClicked">
|
||||||
|
<el-button size="small"
|
||||||
|
>职位详情筛选模板(按职类区分)
|
||||||
|
<el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
|
></el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu
|
||||||
|
:style="{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr 1fr'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-for="item in expectJobFilterTemplateList"
|
||||||
|
:key="item.name"
|
||||||
|
:command="item"
|
||||||
|
>{{ item.name }}</el-dropdown-item
|
||||||
|
>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '10px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div flex-1>
|
||||||
|
<el-form-item mb0 prop="expectJobNameRegExpStr">
|
||||||
|
<div font-size-12px>职位名称/类型/描述 正则匹配筛选逻辑</div>
|
||||||
|
<el-select
|
||||||
|
v-model="formContent.jobDetailRegExpMatchLogic"
|
||||||
|
@change="(value) => gtagRenderer('job_detail_re_ml_change', { value })"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="op in jobDetailRegExpMatchLogicOptions"
|
||||||
|
:key="op.value"
|
||||||
|
:label="op.name"
|
||||||
|
:value="op.value"
|
||||||
|
>{{ op.name }}</el-option
|
||||||
|
>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
width: '100%',
|
||||||
|
height: '1px',
|
||||||
|
backgroundColor: '#f0f0f0',
|
||||||
|
marginTop: '0.5lh'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '2em 1fr',
|
||||||
|
gap: '5px',
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'end'
|
||||||
|
}"
|
||||||
|
class="job-detail-filter-wrap"
|
||||||
|
>
|
||||||
|
<div></div>
|
||||||
|
<el-tooltip
|
||||||
|
effect="light"
|
||||||
|
placement="bottom"
|
||||||
|
@show="gtagRenderer('tooltip_show_about_how_to_fill_df')"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div w-800px>
|
||||||
|
<div style="margin-top: 4px; margin-bottom: 4px">
|
||||||
|
上方“职位名称/类型/描述
|
||||||
|
正则匹配筛选逻辑”配置,你可以自行决定如下三个正则“所有正则匹配时才认为职位匹配”还是“任一正则匹配时即认为职位匹配”。
|
||||||
|
</div>
|
||||||
|
<ul m0>
|
||||||
|
<li>
|
||||||
|
当选择“所有正则匹配时才认为职位匹配”规则时,如果你留空某个输入框,表示任何职位一定匹配这个条件。
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
当选择“任一正则匹配时即认为职位匹配”规则时,如果你留空某个输入框,表示任何职位一定不匹配这个条件。
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<b>请注意</b>,如果<span color-orange>三个输入框均留空</span
|
||||||
|
>,无论上方“职位名称/类型/描述 正则匹配筛选逻辑”配置是什么,都表示<span
|
||||||
|
color-orange
|
||||||
|
>列表中出现的任意职位都将认为同时匹配这三个条件(即不根据职位名称/类型/描述进行筛选)</span
|
||||||
|
>。<br />
|
||||||
|
因此,可以按照如下场景填写你对于期望职位的筛选条件:
|
||||||
|
<ul style="margin-top: 4px; margin-bottom: 4px">
|
||||||
|
<li>
|
||||||
|
如果你只考虑工作类型,请填写“职位类型正则”输入框,其余两个输入框清空。这可以确保求职方向基本正确。
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
如果你着重关注职位描述,请填写“职位描述正则”,其余两个输入框酌情填写。
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
如果你想开聊列表里的推荐的任意职位,不根据职位名称/类型/描述进行筛选,请清空这三个输入框。
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div>
|
||||||
|
你可以在右侧"职位详情筛选模板"选择一个模板,并在选中模板基础上尝试修改
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>“职位类型正则”填写过程中请注意</b
|
||||||
|
>,“职位类型”是由BOSS直聘预定义好的一系列职位分类,因此<b>请按照这个分类来编写正则</b>。<br />
|
||||||
|
<!-- 这个分类可以在此找到:<br />
|
||||||
|
<img w-400px src="../resources/job-type-source-entry.png" /> -->
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
关于误伤/误投的排查,<a
|
||||||
|
href="javascript:;"
|
||||||
|
style="color: var(--el-color-primary)"
|
||||||
|
@click.prevent="handleHowToFillDetailFilterClick"
|
||||||
|
>请参阅这个链接</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
font-size-12px
|
||||||
|
:style="{
|
||||||
|
width: 'fit-content',
|
||||||
|
padding: '0',
|
||||||
|
height: 'auto',
|
||||||
|
position: 'relative',
|
||||||
|
top: '6px'
|
||||||
|
}"
|
||||||
|
><span><QuestionFilled w-1em h-1em mr2px /></span
|
||||||
|
>如下三个输入框工作机制是怎样的?怎样填写?误伤/误投如何排查?</el-button
|
||||||
|
>
|
||||||
|
</el-tooltip>
|
||||||
|
<div></div>
|
||||||
|
<el-form-item mb0 prop="expectJobNameRegExpStr">
|
||||||
|
<div font-size-12px>职位名称正则(不区分大小写)</div>
|
||||||
|
<el-input
|
||||||
|
v-model="formContent.expectJobNameRegExpStr"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="
|
||||||
|
getJobDetailRegExpMatchLogicConfig({ formContent }).inputPlaceholderText
|
||||||
|
"
|
||||||
|
:autosize="{ minRows: 2 }"
|
||||||
|
max-h-6lh
|
||||||
|
@blur="
|
||||||
|
formContent.expectJobNameRegExpStr =
|
||||||
|
formContent.expectJobNameRegExpStr?.trim() ?? ''
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<div
|
||||||
|
mb0px
|
||||||
|
font-size-12px
|
||||||
|
flex
|
||||||
|
flex-justify-center
|
||||||
|
fw-800
|
||||||
|
flex-self-start
|
||||||
|
position-relative
|
||||||
|
style="top: 42px"
|
||||||
|
>
|
||||||
|
{{ getJobDetailRegExpMatchLogicConfig({ formContent }).logicText }}
|
||||||
|
</div>
|
||||||
|
<el-form-item mb0 prop="expectJobTypeRegExpStr">
|
||||||
|
<div ref="jobDetailRegExpSectionEl" font-size-12px>
|
||||||
|
职位类型正则(推荐填写,不区分大小写)
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="formContent.expectJobTypeRegExpStr"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="
|
||||||
|
getJobDetailRegExpMatchLogicConfig({ formContent }).inputPlaceholderText
|
||||||
|
"
|
||||||
|
:autosize="{ minRows: 2 }"
|
||||||
|
max-h-6lh
|
||||||
|
@blur="
|
||||||
|
formContent.expectJobTypeRegExpStr =
|
||||||
|
formContent.expectJobTypeRegExpStr?.trim() ?? ''
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<div
|
||||||
|
mb0px
|
||||||
|
font-size-12px
|
||||||
|
flex
|
||||||
|
flex-justify-center
|
||||||
|
fw-800
|
||||||
|
flex-self-start
|
||||||
|
position-relative
|
||||||
|
style="top: 42px"
|
||||||
|
>
|
||||||
|
{{ getJobDetailRegExpMatchLogicConfig({ formContent }).logicText }}
|
||||||
|
</div>
|
||||||
|
<el-form-item mb0 prop="expectJobDescRegExpStr">
|
||||||
|
<div font-size-12px>职位描述正则(不区分大小写)</div>
|
||||||
|
<el-input
|
||||||
|
v-model="formContent.expectJobDescRegExpStr"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="
|
||||||
|
getJobDetailRegExpMatchLogicConfig({ formContent }).inputPlaceholderText
|
||||||
|
"
|
||||||
|
:autosize="{ minRows: 2 }"
|
||||||
|
max-h-6lh
|
||||||
|
@blur="
|
||||||
|
formContent.expectJobDescRegExpStr =
|
||||||
|
formContent.expectJobDescRegExpStr?.trim() ?? ''
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div class="bg-#f8f8f8 pb10px pt10px">
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'end',
|
||||||
|
maxWidth: '800px',
|
||||||
|
margin: '0 auto',
|
||||||
|
paddingLeft: '20px',
|
||||||
|
paddingRight: 'calc(20px + 16px)'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||||
|
import { JobDetailRegExpMatchLogic, SalaryCalculateWay } from '@geekgeekrun/sqlite-plugin/src/enums'
|
||||||
|
import CityChooser from '../MainLayout/GeekAutoStartChatWithBoss/components/CityChooser.vue'
|
||||||
|
import { QuestionFilled, ArrowDown } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getJobDetailRegExpMatchLogicConfig,
|
||||||
|
expectSalaryCalculateWayOption,
|
||||||
|
ensureSalaryRangeCorrect,
|
||||||
|
expectCompanyTemplateList,
|
||||||
|
blockCompanyNameRegExpTemplateList,
|
||||||
|
getHandlerForBlockCompanyNameRegExpTemplateClicked,
|
||||||
|
getHandlerForExpectCompanyTemplateClicked,
|
||||||
|
getHandlerForExpectJobFilterTemplateClicked,
|
||||||
|
getRuleOfExpectJobNameRegExpStr,
|
||||||
|
getRuleOfExpectJobDescRegExpStr,
|
||||||
|
getRuleOfExpectJobTypeRegExpStr,
|
||||||
|
getRuleOfBlockCompanyNameRegExpStr,
|
||||||
|
jobDetailRegExpMatchLogicOptions,
|
||||||
|
getHandlerForExpectSalaryCalculateWayChanged,
|
||||||
|
normalizeCommaSplittedStr
|
||||||
|
} from '../MainLayout/GeekAutoStartChatWithBoss/common'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import expectJobFilterTemplateList from '../MainLayout/GeekAutoStartChatWithBoss/expectJobFilterTemplateList'
|
||||||
|
const { ipcRenderer } = window.electron
|
||||||
|
const gtagRenderer = (name, params?: object) => {
|
||||||
|
return baseGtagRenderer(name, {
|
||||||
|
scene: 'cjc_config',
|
||||||
|
...params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formContent = ref({
|
||||||
|
expectCompanies: '',
|
||||||
|
expectCityList: [],
|
||||||
|
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY,
|
||||||
|
expectJobNameRegExpStr: '',
|
||||||
|
expectJobTypeRegExpStr: '',
|
||||||
|
expectJobDescRegExpStr: '',
|
||||||
|
expectSalaryCalculateWay: SalaryCalculateWay.ANNUAL_PACKAGE,
|
||||||
|
expectSalaryHigh: null,
|
||||||
|
expectSalaryLow: null,
|
||||||
|
blockCompanyNameRegExpStr: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const jobDetailRegExpSectionEl = ref<HTMLDivElement>()
|
||||||
|
const blockCompanyNameRegExpSectionEl = ref<HTMLDivElement>()
|
||||||
|
const formRules = computed(() => ({
|
||||||
|
expectJobNameRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: getRuleOfExpectJobNameRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
|
||||||
|
},
|
||||||
|
expectJobTypeRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: getRuleOfExpectJobTypeRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
|
||||||
|
},
|
||||||
|
expectJobDescRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: getRuleOfExpectJobDescRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
|
||||||
|
},
|
||||||
|
blockCompanyNameRegExpStr: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: getRuleOfBlockCompanyNameRegExpStr({ gtagRenderer, blockCompanyNameRegExpSectionEl })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleBlockCompanyNameRegExpTemplateClicked =
|
||||||
|
getHandlerForBlockCompanyNameRegExpTemplateClicked({
|
||||||
|
gtagRenderer,
|
||||||
|
formContent
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleExpectCompanyTemplateClicked = getHandlerForExpectCompanyTemplateClicked({
|
||||||
|
gtagRenderer,
|
||||||
|
formContent
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleExpectJobFilterTemplateClicked = getHandlerForExpectJobFilterTemplateClicked({
|
||||||
|
gtagRenderer,
|
||||||
|
formContent
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleHowToFillDetailFilterClick() {
|
||||||
|
gtagRenderer('click_linux_do_how_to_fill_df')
|
||||||
|
electron.ipcRenderer.send(
|
||||||
|
'open-external-link',
|
||||||
|
'https://linux.do/t/topic/640626/74?u=geekgeekrun'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleExpectSalaryCalculateWayChanged = getHandlerForExpectSalaryCalculateWayChanged({
|
||||||
|
gtagRenderer,
|
||||||
|
formContent
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
//
|
||||||
|
window.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
async function handleSave() {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
await ipcRenderer.invoke(
|
||||||
|
'save-common-job-condition-config',
|
||||||
|
JSON.parse(
|
||||||
|
JSON.stringify({
|
||||||
|
...formContent.value,
|
||||||
|
expectCompanies: formContent.value.expectCompanies
|
||||||
|
? formContent.value.expectCompanies.split(',').map((s) => s.trim())
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ipcRenderer.send('common-job-condition-config-done')
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||||
|
const commonJobConditionConfig = res.config?.['common-job-condition-config.json'] ?? {}
|
||||||
|
Object.keys(formContent.value).forEach((key) => {
|
||||||
|
if (key in commonJobConditionConfig) {
|
||||||
|
if (key === 'expectCompanies') {
|
||||||
|
formContent.value[key] = (commonJobConditionConfig[key] ?? []).join(',')
|
||||||
|
} else {
|
||||||
|
formContent.value[key] = commonJobConditionConfig[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.common-job-condition-config .el-form-item__error.el-form-item__error--inline {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -52,18 +52,20 @@
|
|||||||
>)。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
|
>)。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
|
||||||
</ElCheckbox>
|
</ElCheckbox>
|
||||||
<ElCheckbox :label="4" :class="[unreadItemsAfterClickSubmit[4] ? 'unread' : '']">
|
<ElCheckbox :label="4" :class="[unreadItemsAfterClickSubmit[4] ? 'unread' : '']">
|
||||||
您所在公司可能会对您的计算机终端或网络进行<b color-red>监控</b>,从而<b color-red
|
您所在公司可能会采购上网行为监控工具或网关(例如奇安信、深信服、绿盟等厂商的产品),对您的计算机终端或网络进行<b
|
||||||
>审计、跟踪</b
|
color-red
|
||||||
>您的行为;上级 / HRBP 团队可能会从 IT
|
>监控</b
|
||||||
团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级 / HRBP
|
>,从而<b color-red>审计、跟踪</b>您的行为;您的上级/IT/HR
|
||||||
团队了解到您正在求职,建议您<b color-red
|
可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR
|
||||||
|
了解到您正在求职,建议您<b color-red
|
||||||
>不要在您所在公司提供的计算机终端或网络上使用本程序</b
|
>不要在您所在公司提供的计算机终端或网络上使用本程序</b
|
||||||
>。
|
>。
|
||||||
</ElCheckbox>
|
</ElCheckbox>
|
||||||
<ElCheckbox :label="5" :class="[unreadItemsAfterClickSubmit[5] ? 'unread' : '']">
|
<ElCheckbox :label="5" :class="[unreadItemsAfterClickSubmit[5] ? 'unread' : '']">
|
||||||
本程序尊重您的隐私,<b color-red
|
本程序尊重您的隐私,<b color-red
|
||||||
>不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及上级 / HRBP
|
>不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及您的上级/IT/HR报告您的求职行为、不会向猎头公司泄露您的信息</b
|
||||||
团队报告您的求职行为、不会向猎头公司泄露您的信息</b
|
>。但由于本程序开源,<b color-red
|
||||||
|
>任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序</b
|
||||||
>。
|
>。
|
||||||
</ElCheckbox>
|
</ElCheckbox>
|
||||||
|
|
||||||
|
|||||||
@@ -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(',')
|
||||||
|
}
|
||||||
+808
-458
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div class="group-item">
|
||||||
|
<div class="group-title">逛BOSS</div>
|
||||||
|
<div flex flex-col class="link-list">
|
||||||
|
<RouterLink to="./GeekAutoStartChatWithBoss">
|
||||||
|
自动开聊
|
||||||
|
<el-tooltip
|
||||||
|
placement="right"
|
||||||
|
:enterable="false"
|
||||||
|
@show="gtagRenderer('tooltip_show_for_nav_boss_b_entry')"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div w-480px>
|
||||||
|
<div>扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。</div>
|
||||||
|
<br />
|
||||||
|
<div>匹配步骤</div>
|
||||||
|
<ol m0 pl2em>
|
||||||
|
<li>
|
||||||
|
按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
检查BOSS活跃度
|
||||||
|
<ul pl2em>
|
||||||
|
<li>
|
||||||
|
如果BOSS活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
对职位名称、职位类型、职位描述进行匹配
|
||||||
|
<ul pl2em>
|
||||||
|
<li>如果匹配则自动点击开聊按钮</li>
|
||||||
|
<li>
|
||||||
|
不匹配则标记这个职位为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<br />
|
||||||
|
<div>异常情况</div>
|
||||||
|
<ol m0 pl2em>
|
||||||
|
<li>
|
||||||
|
当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<QuestionFilled w-1em h-1em mr10px />
|
||||||
|
</el-tooltip>
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink to="./ReadNoReplyReminder">
|
||||||
|
已读不回自动复聊
|
||||||
|
<el-tooltip
|
||||||
|
placement="right"
|
||||||
|
:enterable="false"
|
||||||
|
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div w-480px>
|
||||||
|
<div>
|
||||||
|
BOSS不明原因已读不回?简历就是投不出去?<br />
|
||||||
|
已读不回自动复聊,提醒一下已读不回的 BOSS,助力把握每次机会
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>匹配逻辑</div>
|
||||||
|
<div>在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时:</div>
|
||||||
|
<ul m0 pl2em>
|
||||||
|
<li>如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查</li>
|
||||||
|
<li>
|
||||||
|
如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br />
|
||||||
|
<div>发送内容</div>
|
||||||
|
<ul m0 pl2em>
|
||||||
|
<li>“[盼回复]”表情</li>
|
||||||
|
<li>由大语言模型(根据简历及当前聊天上下文)生成的内容</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<QuestionFilled w-1em h-1em mr10px />
|
||||||
|
</el-tooltip>
|
||||||
|
</RouterLink>
|
||||||
|
<a href="javascript:void(0)" @click="handleClickLaunchBossLogin">
|
||||||
|
编辑登录凭据<TopRight w-1em h-1em mr10px />
|
||||||
|
</a>
|
||||||
|
<a href="javascript:void(0)" @click="handleLaunchBossSite">
|
||||||
|
手动逛<TopRight w-1em h-1em mr10px />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||||
|
import { debounce } from 'lodash'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const handleClickLaunchBossLogin = async () => {
|
||||||
|
gtagRenderer('launch_login_clicked')
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke('login-with-cookie-assistant')
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '登录凭据保存成功'
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLaunchBossSite = debounce(
|
||||||
|
async () => {
|
||||||
|
gtagRenderer('launch_boss_site_clicked')
|
||||||
|
return await electron.ipcRenderer.invoke('open-site-with-boss-cookie', {
|
||||||
|
url: `https://www.zhipin.com/`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
{ leading: true, trailing: false }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss" src="./style.scss"></style>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div class="group-item">
|
||||||
|
<div class="group-title">全局设置</div>
|
||||||
|
<div flex flex-col class="link-list">
|
||||||
|
<a href="javascript:void(0)" @click="handleClickConfigCommonJobCondition">
|
||||||
|
公共职位筛选条件
|
||||||
|
</a>
|
||||||
|
<a href="javascript:void(0)" @click="handleClickBrowserSetting">
|
||||||
|
编辑浏览器偏好<TopRight w-1em h-1em mr10px />
|
||||||
|
</a>
|
||||||
|
<a href="javascript:void(0)" @click="handleClickConfigLlm">
|
||||||
|
配置大语言模型
|
||||||
|
<div>
|
||||||
|
<el-tooltip
|
||||||
|
placement="right"
|
||||||
|
:enterable="false"
|
||||||
|
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="font-size-12px">
|
||||||
|
支持
|
||||||
|
<span
|
||||||
|
class="pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
||||||
|
style="background-color: #3c4efd"
|
||||||
|
>DeepSeek-V3</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ml4px pl6px pr6px pt4px pb2px color-black border-rd-full font-size-0.8em"
|
||||||
|
style="background-color: #fff"
|
||||||
|
>GPT-4o mini</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ml4px pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
||||||
|
style="background-color: #462ac4"
|
||||||
|
>Qwen2.5</span
|
||||||
|
>
|
||||||
|
模型<br />支持多个“服务商-模型”组合按权重搭配使用
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<QuestionFilled w-1em h-1em mr10px />
|
||||||
|
</el-tooltip>
|
||||||
|
<TopRight w-1em h-1em mr10px />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const handleClickBrowserSetting = async () => {
|
||||||
|
gtagRenderer('browser_setting_clicked')
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke('config-with-browser-assistant')
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '浏览器偏好保存成功'
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickConfigLlm = async () => {
|
||||||
|
gtagRenderer('config_llm_clicked')
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke('llm-config')
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickConfigCommonJobCondition = async () => {
|
||||||
|
gtagRenderer('config_cjc_clicked', { entry: 'left-nav' })
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke('common-job-condition-config')
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss" src="./style.scss"></style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="group-item">
|
||||||
|
<div class="group-title">运行数据</div>
|
||||||
|
<div flex flex-col class="link-list">
|
||||||
|
<RouterLink to="./StartChatRecord">开聊记录</RouterLink>
|
||||||
|
<RouterLink to="./MarkAsNotSuitRecord">标记不合适记录</RouterLink>
|
||||||
|
<RouterLink to="./JobLibrary">职位库</RouterLink>
|
||||||
|
<RouterLink to="./BossLibrary">BOSS库</RouterLink>
|
||||||
|
<RouterLink to="./CompanyLibrary">公司库</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss" src="./style.scss"></style>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,11 @@
|
|||||||
>
|
>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div>
|
<div>
|
||||||
<el-checkbox v-if="!expectJobTypeRegExpStr?.trim()" :model-value="false" disabled>
|
<el-checkbox
|
||||||
|
v-if="!expectJobTypeRegExpStrForRender?.trim()"
|
||||||
|
:model-value="false"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
|
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -17,7 +21,7 @@
|
|||||||
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
|
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
<div ml1.5em color-gray>
|
<div ml1.5em color-gray>
|
||||||
<div>当前职位类型正则:{{ expectJobTypeRegExpStr?.trim() }}</div>
|
<div>当前职位类型正则:{{ expectJobTypeRegExpStrForRender?.trim() }}</div>
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
formContent.autoReminder.rechatContentSource ===
|
formContent.autoReminder.rechatContentSource ===
|
||||||
@@ -33,7 +37,11 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div>
|
<div>
|
||||||
<el-checkbox v-if="!blockCompanyNameRegExpStr?.trim()" :model-value="false" disabled>
|
<el-checkbox
|
||||||
|
v-if="!blockCompanyNameRegExpStrForRender?.trim()"
|
||||||
|
:model-value="false"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
|
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -41,7 +49,7 @@
|
|||||||
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
|
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
<div ml1.5em color-gray>
|
<div ml1.5em color-gray>
|
||||||
<div>当前不期望投递公司正则:{{ blockCompanyNameRegExpStr?.trim() }}</div>
|
<div>当前不期望投递公司正则:{{ blockCompanyNameRegExpStrForRender?.trim() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -313,10 +321,13 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
|||||||
|
|
||||||
const expectJobTypeRegExpStr = ref('')
|
const expectJobTypeRegExpStr = ref('')
|
||||||
const blockCompanyNameRegExpStr = ref('')
|
const blockCompanyNameRegExpStr = ref('')
|
||||||
|
const fieldsForUseCommonConfig = ref({})
|
||||||
async function fetchAutoStartChatConfig() {
|
async function fetchAutoStartChatConfig() {
|
||||||
await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||||
expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr
|
expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr
|
||||||
blockCompanyNameRegExpStr.value = res.config['boss.json']?.blockCompanyNameRegExpStr
|
blockCompanyNameRegExpStr.value = res.config['boss.json']?.blockCompanyNameRegExpStr
|
||||||
|
fieldsForUseCommonConfig.value = res.config['boss.json']?.fieldsForUseCommonConfig ?? {}
|
||||||
|
commonJobConditionConfig.value = res.config['common-job-condition-config.json']
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fetchAutoStartChatConfig()
|
fetchAutoStartChatConfig()
|
||||||
@@ -324,6 +335,30 @@ mittBus.on('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig)
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
mittBus.off('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig)
|
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)
|
const resumeContent = ref(null)
|
||||||
async function fetchResumeContent() {
|
async function fetchResumeContent() {
|
||||||
|
|||||||
@@ -3,153 +3,11 @@
|
|||||||
<div class="flex flex-col min-w200px w200px pt30px pl30px aside-nav of-hidden">
|
<div class="flex flex-col min-w200px w200px pt30px pl30px aside-nav of-hidden">
|
||||||
<div class="nav-list flex-1 of-auto pl20px ml--20px">
|
<div class="nav-list flex-1 of-auto pl20px ml--20px">
|
||||||
<RouterLink v-show="false" to="./TaskManager">任务管理</RouterLink>
|
<RouterLink v-show="false" to="./TaskManager">任务管理</RouterLink>
|
||||||
<div class="group-item">
|
<BossPart />
|
||||||
<div class="group-title">BOSS直聘</div>
|
|
||||||
<div flex flex-col class="link-list">
|
|
||||||
<RouterLink to="./GeekAutoStartChatWithBoss">
|
|
||||||
自动开聊
|
|
||||||
<el-tooltip
|
|
||||||
placement="right"
|
|
||||||
:enterable="false"
|
|
||||||
@show="gtagRenderer('tooltip_show_for_nav_boss_b_entry')"
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<div w-480px>
|
|
||||||
<div>扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。</div>
|
|
||||||
<br />
|
|
||||||
<div>匹配步骤</div>
|
|
||||||
<ol m0 pl2em>
|
|
||||||
<li>
|
|
||||||
按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
检查BOSS活跃度
|
|
||||||
<ul pl2em>
|
|
||||||
<li>
|
|
||||||
如果BOSS活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
对职位名称、职位类型、职位描述进行匹配
|
|
||||||
<ul pl2em>
|
|
||||||
<li>如果匹配则自动点击开聊按钮</li>
|
|
||||||
<li>
|
|
||||||
不匹配则标记这个职位为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<br />
|
|
||||||
<div>异常情况</div>
|
|
||||||
<ol m0 pl2em>
|
|
||||||
<li>
|
|
||||||
当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<QuestionFilled w-1em h-1em mr10px />
|
|
||||||
</el-tooltip>
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink to="./ReadNoReplyReminder">
|
|
||||||
已读不回自动复聊
|
|
||||||
<el-tooltip
|
|
||||||
placement="right"
|
|
||||||
:enterable="false"
|
|
||||||
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<div w-480px>
|
|
||||||
<div>
|
|
||||||
BOSS不明原因已读不回?简历就是投不出去?<br />
|
|
||||||
已读不回自动复聊,提醒一下已读不回的 BOSS,助力把握每次机会
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div>匹配逻辑</div>
|
|
||||||
<div>在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时:</div>
|
|
||||||
<ul m0 pl2em>
|
|
||||||
<li>如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查</li>
|
|
||||||
<li>
|
|
||||||
如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<br />
|
|
||||||
<div>发送内容</div>
|
|
||||||
<ul m0 pl2em>
|
|
||||||
<li>“[盼回复]”表情</li>
|
|
||||||
<li>由大语言模型(根据简历及当前聊天上下文)生成的内容</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<QuestionFilled w-1em h-1em mr10px />
|
|
||||||
</el-tooltip>
|
|
||||||
</RouterLink>
|
|
||||||
<a href="javascript:void(0)" @click="handleClickLaunchBossLogin">
|
|
||||||
编辑登录凭据<TopRight w-1em h-1em mr10px />
|
|
||||||
</a>
|
|
||||||
<a href="javascript:void(0)" @click="handleLaunchBossSite">
|
|
||||||
手动逛<TopRight w-1em h-1em mr10px />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="group-divider" />
|
<hr class="group-divider" />
|
||||||
<div class="group-item">
|
<GlobalConfigPart />
|
||||||
<div class="group-title">全局设置</div>
|
|
||||||
<div flex flex-col class="link-list">
|
|
||||||
<a href="javascript:void(0)" @click="handleClickBrowserSetting">
|
|
||||||
编辑浏览器偏好<TopRight w-1em h-1em mr10px />
|
|
||||||
</a>
|
|
||||||
<a href="javascript:void(0)" @click="handleClickConfigLlm">
|
|
||||||
配置大语言模型
|
|
||||||
<div>
|
|
||||||
<el-tooltip
|
|
||||||
placement="right"
|
|
||||||
:enterable="false"
|
|
||||||
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<div class="font-size-12px">
|
|
||||||
支持
|
|
||||||
<span
|
|
||||||
class="pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
|
||||||
style="background-color: #3c4efd"
|
|
||||||
>DeepSeek-V3</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="ml4px pl6px pr6px pt4px pb2px color-black border-rd-full font-size-0.8em"
|
|
||||||
style="background-color: #fff"
|
|
||||||
>GPT-4o mini</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="ml4px pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
|
||||||
style="background-color: #462ac4"
|
|
||||||
>Qwen2.5</span
|
|
||||||
>
|
|
||||||
模型<br />支持多个“服务商-模型”组合按权重搭配使用
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<QuestionFilled w-1em h-1em mr10px />
|
|
||||||
</el-tooltip>
|
|
||||||
<TopRight w-1em h-1em mr10px />
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="group-divider" />
|
<hr class="group-divider" />
|
||||||
<div class="group-item">
|
<RunDataRecordPart />
|
||||||
<div class="group-title">运行数据</div>
|
|
||||||
<div flex flex-col class="link-list">
|
|
||||||
<RouterLink to="./StartChatRecord">开聊记录</RouterLink>
|
|
||||||
<RouterLink to="./MarkAsNotSuitRecord">标记不合适记录</RouterLink>
|
|
||||||
<RouterLink to="./JobLibrary">职位库</RouterLink>
|
|
||||||
<RouterLink to="./BossLibrary">BOSS库</RouterLink>
|
|
||||||
<RouterLink to="./CompanyLibrary">公司库</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-16px pb-16px flex-0 font-size-12px">
|
<div class="pt-16px pb-16px flex-0 font-size-12px">
|
||||||
<div v-if="updateStore.availableNewRelease" mb16px>
|
<div v-if="updateStore.availableNewRelease" mb16px>
|
||||||
@@ -204,12 +62,12 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
|
||||||
import useBuildInfo from '@renderer/hooks/useBuildInfo'
|
import useBuildInfo from '@renderer/hooks/useBuildInfo'
|
||||||
import { debounce } from 'lodash'
|
|
||||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||||
import { useUpdateStore, useTaskManagerStore } from '../../store/index'
|
import { useUpdateStore, useTaskManagerStore } from '../../store/index'
|
||||||
import { ElMessage } from 'element-plus'
|
import BossPart from './LeftNavBar/BossPart.vue'
|
||||||
|
import GlobalConfigPart from './LeftNavBar/GlabalConfigPart.vue'
|
||||||
|
import RunDataRecordPart from './LeftNavBar/RunDataRecordPart.vue'
|
||||||
|
|
||||||
useRouter()
|
useRouter()
|
||||||
|
|
||||||
@@ -223,17 +81,6 @@ const handleGotoProjectPageClick = () => {
|
|||||||
electron.ipcRenderer.send('open-external-link', 'https://github.com/geekgeekrun/geekgeekrun')
|
electron.ipcRenderer.send('open-external-link', 'https://github.com/geekgeekrun/geekgeekrun')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLaunchBossSite = debounce(
|
|
||||||
async () => {
|
|
||||||
gtagRenderer('launch_boss_site_clicked')
|
|
||||||
return await electron.ipcRenderer.invoke('open-site-with-boss-cookie', {
|
|
||||||
url: `https://www.zhipin.com/`
|
|
||||||
})
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
{ leading: true, trailing: false }
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateStore = useUpdateStore()
|
const updateStore = useUpdateStore()
|
||||||
function handleDownloadNewReleaseClick() {
|
function handleDownloadNewReleaseClick() {
|
||||||
gtagRenderer('click_download_release_form_nav')
|
gtagRenderer('click_download_release_form_nav')
|
||||||
@@ -246,41 +93,6 @@ function handleViewNewReleaseClick() {
|
|||||||
|
|
||||||
const taskManagerStore = useTaskManagerStore()
|
const taskManagerStore = useTaskManagerStore()
|
||||||
void taskManagerStore
|
void taskManagerStore
|
||||||
|
|
||||||
const handleClickLaunchBossLogin = async () => {
|
|
||||||
gtagRenderer('launch_login_clicked')
|
|
||||||
try {
|
|
||||||
await electron.ipcRenderer.invoke('login-with-cookie-assistant')
|
|
||||||
ElMessage({
|
|
||||||
type: 'success',
|
|
||||||
message: '登录凭据保存成功'
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClickBrowserSetting = async () => {
|
|
||||||
gtagRenderer('browser_setting_clicked')
|
|
||||||
try {
|
|
||||||
await electron.ipcRenderer.invoke('config-with-browser-assistant')
|
|
||||||
ElMessage({
|
|
||||||
type: 'success',
|
|
||||||
message: '浏览器偏好保存成功'
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClickConfigLlm = async () => {
|
|
||||||
gtagRenderer('config_llm_clicked')
|
|
||||||
try {
|
|
||||||
await electron.ipcRenderer.invoke('llm-config')
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -296,32 +108,6 @@ const handleClickConfigLlm = async () => {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.feedback-button-area,
|
.feedback-button-area,
|
||||||
.update-button-area {
|
.update-button-area {
|
||||||
|
|||||||
@@ -59,6 +59,13 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
title: '已读不回自动复聊 大语言模型测试'
|
title: '已读不回自动复聊 大语言模型测试'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/commonJobConditionConfig',
|
||||||
|
component: () => import('@renderer/page/CommonJobConditionConfig/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '公共职位筛选条件'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/main-layout',
|
path: '/main-layout',
|
||||||
component: () => import('@renderer/page/MainLayout/index.vue'),
|
component: () => import('@renderer/page/MainLayout/index.vue'),
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export function hasIntersection(interval1, interval2) {
|
||||||
|
// 通用函数:将区间标准化,null转换为对应的无穷大
|
||||||
|
const normalizeInterval = (interval) => {
|
||||||
|
const [start, end] = interval;
|
||||||
|
return [
|
||||||
|
[null, undefined].includes(start) ? -Infinity : start,
|
||||||
|
[null, undefined].includes(end) ? Infinity : end
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const [norm1Start, norm1End] = normalizeInterval(interval1);
|
||||||
|
const [norm2Start, norm2End] = normalizeInterval(interval2);
|
||||||
|
|
||||||
|
// 判断交集
|
||||||
|
return Math.max(norm1Start, norm2Start) <= Math.min(norm1End, norm2End);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user