mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-07-12 16:01:38 +08:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b024c6b517 | ||
|
|
9b64f05cb6 | ||
|
|
eefb25a3da | ||
|
|
2dad39c8e4 | ||
|
|
699bdd2641 | ||
|
|
48f1232f7d | ||
|
|
2409699ccf | ||
|
|
64c007066e | ||
|
|
bd503eb268 | ||
|
|
8dc8ca825d | ||
|
|
8e8fb6d331 | ||
|
|
2f3a557fb5 | ||
|
|
780cd73e46 | ||
|
|
6c05128e89 | ||
|
|
e235ae099b | ||
|
|
d479687b8b | ||
|
|
4627b4218e | ||
|
|
b64f411e4a | ||
|
|
12ff9dc4ba | ||
|
|
959c32eb63 | ||
|
|
ce38ecea55 | ||
|
|
340f8946b9 | ||
|
|
59002f73e9 | ||
|
|
5626cf6721 | ||
|
|
fe1c85b38d | ||
|
|
24018f63b5 | ||
|
|
5effcc27ab | ||
|
|
aee6340c6a | ||
|
|
03df5f8d5a | ||
|
|
7d3a0e80d4 | ||
|
|
10f1f150bf | ||
|
|
64249593b8 | ||
|
|
217b2bbf2a | ||
|
|
02667fe2f8 | ||
|
|
5539842392 | ||
|
|
b57e646147 | ||
|
|
6982f6028d | ||
|
|
f4cd4b884b | ||
|
|
61c28ae164 | ||
|
|
ece7fc61e3 | ||
|
|
97793adb0f | ||
|
|
b5c3116c01 | ||
|
|
10cd1cf11e | ||
|
|
7d2e60dfd0 | ||
|
|
6986fbdc3c | ||
|
|
5174da2c6c | ||
|
|
043d782a05 | ||
|
|
67e4b94f9e | ||
|
|
45d14e70fb | ||
|
|
44202b4685 | ||
|
|
00c699d1a6 |
47
README.md
47
README.md
@@ -29,6 +29,16 @@
|
||||
- 当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
|
||||
- 如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
|
||||
|
||||
#### 运行效果
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/672f1637-dd54-416c-922e-1ecf09b0874a
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 已读不回提醒器
|
||||
Boss不明原因已读不回?简历就是投不出去?
|
||||
|
||||
@@ -73,7 +83,7 @@ Boss不明原因已读不回?简历就是投不出去?
|
||||
|
||||
|
||||
### 配置模板选项
|
||||
初次见面,不会配置本程序?当心配置出现问题?本程序为一些关键的功能配置(例如:Boss炸弹中的期望公司设置、职位详情筛选设置;大语言模型设置)加入了了配置模板,让本程序更易用,助你轻松完成配置,开始求职。不妨来试试吧
|
||||
初次见面,不会配置本程序?当心配置出现问题?本程序为一些关键的功能配置(例如:Boss炸弹中的期望公司设置、职位详情筛选设置;大语言模型设置)加入了配置模板,让本程序更易用,助你轻松完成配置,开始求职。不妨来试试吧
|
||||
|
||||
<img width="576" alt="image" src="https://github.com/user-attachments/assets/2cccfece-17e7-4cc2-bf33-67f0ce500625" />
|
||||
|
||||
@@ -108,6 +118,19 @@ Boss不明原因已读不回?简历就是投不出去?
|
||||
```
|
||||
1. 双击应用程序图标以启动程序;启动后,将引导你为初次使用进行一些配置
|
||||
|
||||
## 关于软件不能正常启动、不能按预期运行的一些说明(朋友们帮忙测试一下吧)
|
||||
本程序一直都在不断地更新迭代中,包括新功能的增加以及Bug的修复。
|
||||
|
||||
然而作者本人只有 1台 macOS (Apple Silicon)设备、1台 Windows 设备,且只有作者1人自测(白天工作,晚上熬夜开发、测试),测试条件不是太好(人力不足、设备不足),测试时只要确保两台设备能够正常运行本程序主要流程以及新增逻辑时,就会准出、发版。
|
||||
|
||||
这将不能确保任何设备一定能够正确运行本程序;因此期望朋友们使用过程中,可以帮忙**参与测试**并通过Issue区**反馈问题**。Issue区任何内容我都会看,问题也会尝试进行修复。
|
||||
|
||||
如果部分朋友超级喜欢写代码,或者本身工作就是写代码,可以帮忙下载 [Debugtron](https://github.com/pd4d10/debugtron/releases/tag/v1.0.0-alpha.0) 来调试本程序,查看反馈本程序的日志(如下图)。
|
||||
|
||||

|
||||
|
||||
在此先谢过了~
|
||||
|
||||
## 为什么要写这个程序?
|
||||
### 个人而言
|
||||
以我为例,我真的求职求累了
|
||||
@@ -147,7 +170,19 @@ Boss不明原因已读不回?简历就是投不出去?
|
||||
|
||||
人工筛选这些职位会有很大的心智负担,真的会吐……
|
||||
|
||||
因此,我在Boss炸弹代码中,加入了清理机制,将通过标记不合适的方式,尝试让这些不活跃职位、不合适职位消失;保证只开聊符合你口味的职位
|
||||
因此,我在Boss炸弹代码中,加入了清理机制,将通过标记不合适的方式,尝试让这些不活跃职位、不合适职位消失;保证只开聊符合你口味的职位。
|
||||
|
||||
## 使用必读及免责声明
|
||||
如下是使用必读及免责声明,请您务必逐条阅读;本程序首次启动时,您还会再看到一次;若您不接受如下提到的任何内容,请不要使用本程序。
|
||||
- 本程序从某种程度上说属于辅助工具,与《<a href="https://about.zhipin.com/agreement/?id=registerprotocol_30" rel="noreferer noopener" target="blank">Boss直聘用户协议</a>》(2023年3月版)相关条款相违背,您在注册Boss直聘时已签署过这一条款;根据该条款`七、用户的平台使用义务`、`八、违约责任` 章节,如果一些非正常用户行为被风控监测到,您需要承受包括不仅限于**账号被强制退出登录**、**账号被限制使用**、**账号被封禁**等对您不利的风险;因此使用本程序即意味着**您愿意接受以上风险**,且如果相关风险发生,您需要自行承担相关后果,**本程序概不负责** 。
|
||||
- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的Boss直聘账号**进行求职。
|
||||
- 本程序原理是模拟用户在Boss直聘网页上进行点击操作;Boss直聘网站每过一段时间会发生改版,且有可能包含A/B实验,这将导致本程序相关脚本失效。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
|
||||
- 您的雇主可能会对您的计算机终端或网络进行监控,从而审计、跟踪您的行为;建议您**不要在您雇主提供的计算机终端或网络上使用本程序**。
|
||||
- 本程序需要存储您的登录凭据,即Cookie,来模拟您在Boss直聘上开聊Boss的行为;本程序仅会把您的Cookie存储在本地,并在您访问Boss直聘时将其传输到Boss直聘,**不会泄露给第三方**,也不会进行除自动开聊Boss以外的行为;**请勿向他人泄漏您的Cookie**。
|
||||
- 本程序**不会上报能够识别出您身份的信息**,**不会向您的雇主报告您的求职行为**。
|
||||
- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在Boss直聘上发布;请**自行甄别为您开聊的公司、认真决定是否参加面试、慎重选择Offer**。
|
||||
- 请在Boss直聘上自行**屏蔽您不期望投递的公司**。
|
||||
- 本程序经历过了多次测试,理论上来说大部分情况下可以正常运行;如果您有顾虑,建议通过VMWare WorkStation/Fusion、VirtualBox、Hyper-V/Windows沙盒等虚拟化技术运行本程序。如发生问题,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
|
||||
|
||||
--------
|
||||
|
||||
@@ -156,3 +191,11 @@ Boss不明原因已读不回?简历就是投不出去?
|
||||
更多功能还在开发中~
|
||||
|
||||
祝你求职成功,事业顺利,事事顺心
|
||||
|
||||
## Star 数据 感谢支持
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=geekgeekrun/geekgeekrun&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=geekgeekrun/geekgeekrun&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=geekgeekrun/geekgeekrun&type=Date" />
|
||||
</picture>
|
||||
|
||||
3
packages/geek-auto-start-chat-with-boss/constant.mjs
Normal file
3
packages/geek-auto-start-chat-with-boss/constant.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
export const activeDescList = [
|
||||
'', "半年前活跃","近半年活跃","5月内活跃","4月内活跃","3月内活跃","2月内活跃","本月活跃","2周内活跃","本周活跃","3日内活跃","昨日活跃","今日活跃","刚刚活跃"
|
||||
]
|
||||
@@ -7,12 +7,19 @@
|
||||
"industryList": []
|
||||
},
|
||||
"expectJobRegExpStr": "",
|
||||
"jobNotMatchStrategy": 1,
|
||||
"jobNotActiveStrategy": 1,
|
||||
"markAsNotActiveSelectedTimeRange": 7,
|
||||
"expectCityList": [],
|
||||
"expectCityNotMatchStrategy": 3,
|
||||
"strategyScopeOptionWhenMarkJobCityNotMatch": 2,
|
||||
"autoReminder": {
|
||||
"throttleIntervalMinutes": 10,
|
||||
"rechatLimitDay": 21,
|
||||
"geminiApiKey": "",
|
||||
"rechatContentSource": 1,
|
||||
"recentMessageQuantityForLlm": 8,
|
||||
"rechatLlmFallback": 1
|
||||
"rechatLlmFallback": 1,
|
||||
"onlyRemindBossWithExpectJobType": true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
[
|
||||
"青钱","软通动力","南天","睿服","中电金信","佰钧成","云链","博彦","汉克时代","柯莱特","拓保","亿达信息","纬创","微创","微澜","诚迈科技","法本","兆尹","诚迈","联合永道","新致软件","宇信科技","华为","德科","FESCO","科锐","科之锐",
|
||||
"抖音","字节","字跳","有竹居","脸萌","头条","懂车帝","滴滴","嘀嘀","巨量引擎","小桔","网易","有道","腾讯","酷狗","酷我","阅文","搜狗","京东","沃东天骏","达达","达冠","百度","昆仑芯","小度","度小满","爱奇艺","携程","趣拿","去哪儿","集度","智图","长地万方","瑞图万方","道道通","小熊博望","理想","蔚来","顺丰","讯飞","同程","艺龙","马蜂窝","贝壳","自如","链家","我爱我家","相寓","多点","金山","小米","猎豹","新浪","微博","阿里","淘宝","淘麦郎","天猫","盒马","口碑","优视","夸克","UC","蚂蚁","高德","LAZADA","来赞达","飞猪","菜鸟","哈啰","钉钉","乌鸫","饿了么","美团","三快","猫眼","快手","映客","小红书","行吟","奇虎","360","三六零","鸿盈","奇富","奇元","亚信","启明星辰","奇安信","深信服","长亭","绿盟","天融信","商汤","SenseTime","大华","海康威视","hikvision","汽车之家","车好多","瓜子","易车","昆仑万维","昆仑天工","闲徕","趣加","FunPlus","完美","马上消费","轻松","水滴","白龙马","58","车欢欢","五八","红布林","致美","快狗","天鹅到家","转转","美餐","知乎","智者四海","易点云","搜狐","用友","畅捷通","猿辅导","小猿","猿力","好未来","学而思","希望学","新东方","东方甄选","东方优选","作业帮","高途","跟谁学","学科网","天学网","一起教育","一起作业","美术宝","火花思维","粉笔","老虎国际","一心向上","向上一意","联想","拉勾","乐视","欢聚","竞技世界","拼多多","寻梦","得物","Moka","希瑞亚斯","北森","OPPO","欧珀","vivo","维沃","小天才","步步高","读书郎","货拉拉","陌陌","探探","Shopee","首汽租车","GoFun","神州租车","天眼查","旷视","小冰","美图","智谱华章","MiniMax","石头科技","迅雷","TP","希音","SHEIN","稀宇","深言","百川智能","与爱为舞","牵手","Grab","爱回收","洋钱罐","瓴岳","得到","思维造物","地平线","咪咕","翼支付","电信","天翼","联通","蓝湖","墨刀"
|
||||
"抖音","字节","字跳","有竹居","脸萌","头条","懂车帝","滴滴","嘀嘀","巨量引擎","小桔","网易","有道","腾讯","酷狗","酷我","阅文","搜狗","小鹅通","富途","京东","沃东天骏","达达","达冠","百度","昆仑芯","小度","度小满","爱奇艺","携程","趣拿","去哪儿","集度","智图","长地万方","瑞图万方","道道通","小熊博望","理想","蔚来","顺丰","丰巢","中通","圆通","申通","跨越","讯飞","同程","艺龙","马蜂窝","贝壳","自如","链家","我爱我家","相寓","多点","金山","小米","猎豹","新浪","微博","阿里","淘宝","淘麦郎","天猫","盒马","口碑","优视","夸克","UC","蚂蚁","高德","LAZADA","来赞达","飞猪","菜鸟","哈啰","钉钉","乌鸫","饿了么","美团","三快","猫眼","快手","映客","小红书","行吟","奇虎","360","三六零","鸿盈","奇富","奇元","亚信","启明星辰","奇安信","深信服","长亭","绿盟","天融信","商汤","SenseTime","大华","海康威视","hikvision","汽车之家","车好多","瓜子","易车","昆仑万维","昆仑天工","闲徕","趣加","FunPlus","完美","马上消费","轻松","水滴","白龙马","58","车欢欢","五八","红布林","致美","快狗","天鹅到家","转转","美餐","知乎","智者四海","易点云","搜狐","用友","畅捷通","猿辅导","小猿","猿力","好未来","学而思","希望学","新东方","东方甄选","东方优选","作业帮","高途","跟谁学","学科网","天学网","一起教育","一起作业","美术宝","火花思维","粉笔","51talk","爱学习","高思","老虎国际","一心向上","向上一意","联想","拉勾","乐视","欢聚","竞技世界","拼多多","寻梦","从鲸","TEMU","得物","有赞","Moka","希瑞亚斯","北森","OPPO","欧珀","vivo","维沃","小天才","步步高","读书郎","货拉拉","陌陌","探探","Shopee","虾皮","首汽租车","GoFun","神州租车","天眼查","旷视","小冰","美图","智谱华章","MiniMax","石头科技","迅雷","TP","锐捷","Tenda","腾达","斐讯","希音","SHEIN","稀宇","深言","百川智能","与爱为舞","牵手","Grab","爱回收","洋钱罐","瓴岳","得到","思维造物","地平线","咪咕","翼支付","电信","天翼","联通","蓝湖","墨刀","海尔","美的","米哈游","传音","同花顺","国美","TCL"
|
||||
]
|
||||
@@ -16,8 +16,9 @@ import { calculateTotalCombinations, combineFiltersWithConstraintsGenerator } fr
|
||||
import { default as jobFilterConditions } from './internal-config/job-filter-conditions-20241002.json'
|
||||
import { default as rawIndustryFilterExemption } from './internal-config/job-filter-industry-filter-exemption-20241002.json'
|
||||
import { ChatStartupFrom } from '@geekgeekrun/sqlite-plugin/dist/entity/ChatStartupLog'
|
||||
import { MarkAsNotSuitReason } from '@geekgeekrun/sqlite-plugin/dist/enums'
|
||||
|
||||
import { MarkAsNotSuitReason, MarkAsNotSuitOp, StrategyScopeOptionWhenMarkJobNotMatch, SalaryCalculateWay } from '@geekgeekrun/sqlite-plugin/dist/enums'
|
||||
import { activeDescList } from './constant.mjs'
|
||||
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
|
||||
const jobFilterConditionsMapByCode = {}
|
||||
Object.values(jobFilterConditions).forEach(arr => {
|
||||
arr.forEach(option => {
|
||||
@@ -45,7 +46,6 @@ industryFilterExemption.forEach(item => {
|
||||
ensureConfigFileExist()
|
||||
ensureStorageFileExist()
|
||||
|
||||
const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE)
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
export const autoStartChatEventBus = new EventEmitter()
|
||||
|
||||
@@ -79,6 +79,43 @@ const targetCompanyList = readConfigFile('target-company-list.json').filter(it =
|
||||
|
||||
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
|
||||
const expectJobRegExpStr = readConfigFile('boss.json').expectJobRegExpStr
|
||||
const jobNotMatchStrategy = readConfigFile('boss.json').jobNotMatchStrategy ?? MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
|
||||
const expectCityNotMatchStrategy = readConfigFile('boss.json').expectCityNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||
const expectCityList = readConfigFile('boss.json').expectCityList ?? []
|
||||
|
||||
const strategyScopeOptionWhenMarkJobCityNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobCityNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
// salary
|
||||
const expectSalaryLow = parseFloat(readConfigFile('boss.json').expectSalaryLow) || null
|
||||
const expectSalaryHigh = parseFloat(readConfigFile('boss.json').expectSalaryHigh) || null
|
||||
const expectSalaryCalculateWay = readConfigFile('boss.json').expectSalaryCalculateWay ?? SalaryCalculateWay.MONTH_SALARY
|
||||
const expectSalaryNotMatchStrategy = readConfigFile('boss.json').expectSalaryNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||
const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
|
||||
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
// work exp
|
||||
const expectWorkExpList = readConfigFile('boss.json').expectWorkExpList ?? []
|
||||
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
const markAsNotActiveSelectedTimeRange = (() => {
|
||||
let n = readConfigFile('boss.json').markAsNotActiveSelectedTimeRange
|
||||
if (
|
||||
typeof n !== 'number' || isNaN(parseInt(n)) || n >= activeDescList.length || n < 0
|
||||
) {
|
||||
n = 7
|
||||
}
|
||||
return n
|
||||
})()
|
||||
const jobNotActiveStrategy = (() => {
|
||||
let value = readConfigFile('boss.json').jobNotActiveStrategy ?? MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
if (markAsNotActiveSelectedTimeRange === 0) {
|
||||
value = MarkAsNotSuitOp.NO_OP
|
||||
}
|
||||
return value
|
||||
})()
|
||||
|
||||
let {
|
||||
expectJobNameRegExpStr,
|
||||
expectJobTypeRegExpStr,
|
||||
@@ -121,6 +158,12 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
||||
const result = {}
|
||||
const notSuitableFeedbackButtonProxy = await page.$('.job-detail-box .job-detail-operate .not-suitable')
|
||||
if (notSuitableFeedbackButtonProxy) {
|
||||
await notSuitableFeedbackButtonProxy.evaluate(el => {
|
||||
el.scrollIntoView({
|
||||
block: 'center'
|
||||
})
|
||||
})
|
||||
await sleep(200)
|
||||
await notSuitableFeedbackButtonProxy.click()
|
||||
const rawReasonResData = (await (await page.waitForResponse(
|
||||
response => {
|
||||
@@ -159,6 +202,19 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) {
|
||||
}
|
||||
break
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT:
|
||||
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT:
|
||||
case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: {
|
||||
const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="同城距离远"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="公司不感兴趣"]`))
|
||||
?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||
if (opProxy) {
|
||||
await opProxy.click()
|
||||
isOptionChosen = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_NOT_SUIT:
|
||||
default: {
|
||||
const jobNotSuitOptionProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="职位"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`))
|
||||
@@ -487,22 +543,121 @@ async function toRecommendPage (hooks) {
|
||||
|
||||
// job list
|
||||
const recommendJobListElProxy = await page.$('.job-list-container .rec-job-list')
|
||||
let jobListData = await page.evaluate(`document.querySelector('.page-jobs-main')?.__vue__?.jobList`)
|
||||
let jobListData = []
|
||||
async function updateJobListData () {
|
||||
jobListData = await page.evaluate(`document.querySelector('.page-jobs-main')?.__vue__?.jobList`)
|
||||
// due to city can get from list immediately
|
||||
// so just set those job which city is not suit to blockJobNotSuit
|
||||
// to skip view detail
|
||||
|
||||
// skip invalid salaryData (兼职、日结、实习 etc)
|
||||
jobListData.forEach(it => {
|
||||
const salaryData = parseSalary(it.salaryDesc)
|
||||
if (!salaryData.high || !salaryData.low) {
|
||||
blockJobNotSuit.add(it.encryptJobId)
|
||||
}
|
||||
})
|
||||
if (
|
||||
(
|
||||
expectCityNotMatchStrategy === MarkAsNotSuitOp.NO_OP &&
|
||||
Array.isArray(expectCityList) &&
|
||||
expectCityList.length
|
||||
) ||
|
||||
(
|
||||
expectWorkExpNotMatchStrategy === MarkAsNotSuitOp.NO_OP &&
|
||||
Array.isArray(expectWorkExpList) &&
|
||||
expectWorkExpList.length
|
||||
) ||
|
||||
(
|
||||
strategyScopeOptionWhenMarkSalaryNotMatch === MarkAsNotSuitOp.NO_OP &&
|
||||
isSalaryFilterEnabled
|
||||
)
|
||||
) {
|
||||
console.log(`add job city not suit into blockJobNotSuit set`)
|
||||
for (const it of jobListData) {
|
||||
if (!expectCityList.includes(it.cityName)) {
|
||||
blockJobNotSuit.add(it.encryptJobId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateJobListData()
|
||||
|
||||
let hasReachLastPage = false
|
||||
let targetJobIndex = -1
|
||||
let targetJobData
|
||||
let targetJobData, selectedJobData // they show be same; one is from list, another is from detail
|
||||
function checkIfSalarySuit(salaryDesc) {
|
||||
const salaryData = parseSalary(salaryDesc)
|
||||
if (expectSalaryCalculateWay === SalaryCalculateWay.MONTH_SALARY) {
|
||||
if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
|
||||
return false
|
||||
}
|
||||
if (expectSalaryLow && salaryData.low < expectSalaryLow) {
|
||||
return false
|
||||
}
|
||||
} else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||
const salaryDataMonth = salaryData.month || 12
|
||||
if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
|
||||
return false
|
||||
}
|
||||
if (expectSalaryLow && (salaryData.low * salaryDataMonth) / 10 < expectSalaryLow) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
function getTempTargetJobIndexToCheckDetail () {
|
||||
return jobListData.findIndex(it => {
|
||||
return !blockBossNotNewChat.has(it.encryptBossId) &&
|
||||
!blockBossNotActive.has(it.encryptBossId) &&
|
||||
!blockJobNotSuit.has(it.encryptJobId) &&
|
||||
(
|
||||
(
|
||||
enableCompanyAllowList ?
|
||||
[...expectCompanySet].find(
|
||||
name => it.brandName?.toLowerCase?.()?.includes(name.toLowerCase())
|
||||
)
|
||||
:
|
||||
true
|
||||
) || (
|
||||
// enter job detail to mark as not suit for city filter
|
||||
(
|
||||
Array.isArray(expectCityList) &&
|
||||
expectCityList.length &&
|
||||
[
|
||||
MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS,
|
||||
MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
].includes(expectCityNotMatchStrategy) &&
|
||||
strategyScopeOptionWhenMarkJobCityNotMatch === StrategyScopeOptionWhenMarkJobNotMatch.ALL_JOB
|
||||
) ? !expectCityList.includes(it.cityName) : false
|
||||
) || (
|
||||
// enter job detail to mark as not suit for work exp filter
|
||||
(
|
||||
Array.isArray(expectWorkExpList) &&
|
||||
expectWorkExpList.length &&
|
||||
[
|
||||
MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS,
|
||||
MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
].includes(expectWorkExpNotMatchStrategy) &&
|
||||
strategyScopeOptionWhenMarkJobWorkExpNotMatch === StrategyScopeOptionWhenMarkJobNotMatch.ALL_JOB
|
||||
) ? !expectWorkExpList.includes(it.jobExperience) : false
|
||||
) || (
|
||||
// enter job detail to mark as not suit for salary filter
|
||||
(
|
||||
isSalaryFilterEnabled &&
|
||||
[
|
||||
MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS,
|
||||
MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
].includes(expectSalaryNotMatchStrategy) &&
|
||||
strategyScopeOptionWhenMarkSalaryNotMatch === StrategyScopeOptionWhenMarkJobNotMatch.ALL_JOB
|
||||
) ? !checkIfSalarySuit(it.salaryDesc) : false
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
continueFind: while (targetJobIndex < 0 && !hasReachLastPage) {
|
||||
// when disable company allow list, we will believe that the first one in the list is your expect job.
|
||||
let tempTargetJobIndexToCheckDetail = enableCompanyAllowList ? jobListData.findIndex(
|
||||
it => !blockBossNotNewChat.has(it.encryptBossId)
|
||||
&& !blockBossNotActive.has(it.encryptBossId)
|
||||
&& [...expectCompanySet].find(
|
||||
name => it.brandName.includes(name)
|
||||
)
|
||||
&& !blockJobNotSuit.has(it.encryptJobId)
|
||||
) : jobListData.findIndex(
|
||||
it => !blockBossNotNewChat.has(it.encryptBossId) && !blockBossNotActive.has(it.encryptBossId) && !blockJobNotSuit.has(it.encryptJobId)
|
||||
)
|
||||
let tempTargetJobIndexToCheckDetail = getTempTargetJobIndexToCheckDetail()
|
||||
while (tempTargetJobIndexToCheckDetail < 0 && !hasReachLastPage) {
|
||||
// fetch new
|
||||
const recommendJobListElBBox = await recommendJobListElProxy.boundingBox()
|
||||
@@ -532,17 +687,8 @@ async function toRecommendPage (hooks) {
|
||||
requestNextPagePromiseWithResolver = null
|
||||
|
||||
await sleep(5000)
|
||||
jobListData = await page.evaluate(
|
||||
`
|
||||
document.querySelector('.page-jobs-main')?.__vue__?.jobList
|
||||
`
|
||||
)
|
||||
tempTargetJobIndexToCheckDetail = jobListData.findIndex(it =>
|
||||
!blockBossNotNewChat.has(it.encryptBossId) &&
|
||||
!blockBossNotActive.has(it.encryptBossId) &&
|
||||
[...expectCompanySet].find(name => it.brandName?.toLowerCase?.()?.includes(name.toLowerCase())) &&
|
||||
!blockJobNotSuit.has(it.encryptJobId)
|
||||
)
|
||||
await updateJobListData()
|
||||
tempTargetJobIndexToCheckDetail = getTempTargetJobIndexToCheckDetail()
|
||||
}
|
||||
|
||||
if (tempTargetJobIndexToCheckDetail < 0 && hasReachLastPage) {
|
||||
@@ -584,61 +730,252 @@ async function toRecommendPage (hooks) {
|
||||
await sleepWithRandomDelay(2000)
|
||||
}
|
||||
targetJobData = await page.evaluate('document.querySelector(".job-detail-box").__vue__.data')
|
||||
selectedJobData = await page.evaluate('document.querySelector(".page-jobs-main").__vue__.currentJob')
|
||||
// save the job detail info
|
||||
await hooks.jobDetailIsGetFromRecommendList?.promise(targetJobData)
|
||||
|
||||
//#region collect not suit reasons
|
||||
const notSuitReasonIdToStrategyMap = {}
|
||||
const notSuitConditionHandleMap = {
|
||||
async active() {
|
||||
blockBossNotActive.add(targetJobData.jobInfo.encryptUserId)
|
||||
if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.BOSS_INACTIVE)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.BOSS_INACTIVE,
|
||||
extInfo: {
|
||||
bossActiveTimeDesc: targetJobData.bossInfo.activeTimeDesc,
|
||||
chosenReasonInUi
|
||||
},
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
else if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
try {
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.BOSS_INACTIVE,
|
||||
extInfo: null,
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
},
|
||||
async city() {
|
||||
blockJobNotSuit.add(targetJobData.jobInfo.encryptId)
|
||||
if (expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_CITY_NOT_SUIT)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_CITY_NOT_SUIT,
|
||||
extInfo: {
|
||||
chosenReasonInUi
|
||||
},
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
else if (expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
try {
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_CITY_NOT_SUIT,
|
||||
extInfo: null,
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
},
|
||||
async workExp() {
|
||||
blockJobNotSuit.add(targetJobData.jobInfo.encryptId)
|
||||
if (expectWorkExpNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT,
|
||||
extInfo: {
|
||||
chosenReasonInUi
|
||||
},
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
else if (expectWorkExpNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
try {
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT,
|
||||
extInfo: null,
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
},
|
||||
async jobDetail() {
|
||||
blockJobNotSuit.add(targetJobData.jobInfo.encryptId)
|
||||
if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_NOT_SUIT)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_NOT_SUIT,
|
||||
extInfo: {
|
||||
bossActiveTimeDesc: targetJobData.bossInfo.activeTimeDesc,
|
||||
chosenReasonInUi
|
||||
},
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
else if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
try {
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_NOT_SUIT,
|
||||
extInfo: null,
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
},
|
||||
async salary() {
|
||||
blockJobNotSuit.add(targetJobData.jobInfo.encryptId)
|
||||
if (expectSalaryNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT,
|
||||
extInfo: {
|
||||
salaryDesc: selectedJobData.salaryDesc,
|
||||
chosenReasonInUi
|
||||
},
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
else if (expectSalaryNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
try {
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT,
|
||||
extInfo: {
|
||||
salaryDesc: selectedJobData.salaryDesc,
|
||||
},
|
||||
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#region
|
||||
// null
|
||||
// 刚刚活跃 // 今日活跃 // 昨日活跃 // 3日内活跃 // 本周活跃 // 2周内活跃
|
||||
// 本月活跃 // 2月内活跃 // 3月内活跃 // 4月内活跃 // 5月内活跃 // 近半年活跃 // 半年前活跃
|
||||
//#endregion
|
||||
if ([
|
||||
'本月活跃',
|
||||
'2月内活跃',
|
||||
'3月内活跃',
|
||||
'4月内活跃',
|
||||
'5月内活跃',
|
||||
'近半年活跃',
|
||||
'半年前活跃',
|
||||
].includes(targetJobData.bossInfo.activeTimeDesc)) {
|
||||
blockBossNotActive.add(targetJobData.jobInfo.encryptUserId)
|
||||
const indexOfActiveText = activeDescList.indexOf(targetJobData.bossInfo.activeTimeDesc)
|
||||
if (
|
||||
markAsNotActiveSelectedTimeRange > 0 &&
|
||||
indexOfActiveText > 0 && indexOfActiveText <= markAsNotActiveSelectedTimeRange
|
||||
) {
|
||||
// click prevent recommend button
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.BOSS_INACTIVE)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.BOSS_INACTIVE,
|
||||
extInfo: {
|
||||
bossActiveTimeDesc: targetJobData.bossInfo.activeTimeDesc,
|
||||
chosenReasonInUi
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
continue continueFind
|
||||
notSuitReasonIdToStrategyMap.active = jobNotActiveStrategy
|
||||
}
|
||||
if (
|
||||
(Array.isArray(expectCityList) && expectCityList.length) && !expectCityList.includes(selectedJobData.cityName)
|
||||
) {
|
||||
notSuitReasonIdToStrategyMap.city = expectCityNotMatchStrategy
|
||||
}
|
||||
if (
|
||||
(Array.isArray(expectWorkExpList) && expectWorkExpList.length) && !expectWorkExpList.includes(selectedJobData.jobExperience)
|
||||
) {
|
||||
notSuitReasonIdToStrategyMap.workExp = expectWorkExpNotMatchStrategy
|
||||
}
|
||||
if (
|
||||
!testIfJobTitleOrDescriptionSuit(targetJobData.jobInfo)
|
||||
) {
|
||||
blockJobNotSuit.add(targetJobData.jobInfo.encryptId)
|
||||
try {
|
||||
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_NOT_SUIT)
|
||||
await hooks.jobMarkedAsNotSuit.promise(
|
||||
targetJobData,
|
||||
{
|
||||
markFrom: ChatStartupFrom.AutoFromRecommendList,
|
||||
markReason: MarkAsNotSuitReason.JOB_NOT_SUIT,
|
||||
extInfo: {
|
||||
bossActiveTimeDesc: targetJobData.bossInfo.activeTimeDesc,
|
||||
chosenReasonInUi
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
}
|
||||
notSuitReasonIdToStrategyMap.jobDetail = jobNotMatchStrategy
|
||||
}
|
||||
if (
|
||||
!checkIfSalarySuit(selectedJobData.salaryDesc)
|
||||
) {
|
||||
notSuitReasonIdToStrategyMap.salary = expectSalaryNotMatchStrategy
|
||||
}
|
||||
// #endregion
|
||||
console.log('not suit reason and related strategy: ', notSuitReasonIdToStrategyMap)
|
||||
|
||||
// #region execute mark logic
|
||||
// 1. find the one mark on Boss
|
||||
const markOnBossCondition = Object.keys(notSuitReasonIdToStrategyMap).find(k => notSuitReasonIdToStrategyMap[k] === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS)
|
||||
if (markOnBossCondition) {
|
||||
await notSuitConditionHandleMap[markOnBossCondition]()
|
||||
continue continueFind
|
||||
}
|
||||
// 2. if there is no condition to mark Boss, then find the one mark on local db
|
||||
const markOnLocalDbCondition = Object.keys(notSuitReasonIdToStrategyMap).find(k => notSuitReasonIdToStrategyMap[k] === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL)
|
||||
if (markOnLocalDbCondition) {
|
||||
await notSuitConditionHandleMap[markOnBossCondition]()
|
||||
continue continueFind
|
||||
}
|
||||
// #endregion
|
||||
if (
|
||||
// test company again - when allow list not include target company, just skip
|
||||
enableCompanyAllowList && ![...expectCompanySet].find(
|
||||
name => selectedJobData.brandName?.toLowerCase?.()?.includes(name.toLowerCase())
|
||||
) ||
|
||||
// check if job has been marked as not suit or not active
|
||||
[
|
||||
...blockJobNotSuit,
|
||||
...blockBossNotActive
|
||||
].includes(targetJobData.jobInfo.encryptId)
|
||||
) {
|
||||
// just skip
|
||||
continue continueFind
|
||||
}
|
||||
const startChatButtonInnerHTML = await page.evaluate('document.querySelector(".job-detail-box .op-btn.op-btn-chat")?.innerHTML.trim()')
|
||||
@@ -773,7 +1110,6 @@ export async function mainLoop (hooks) {
|
||||
}
|
||||
})
|
||||
hooks.puppeteerLaunched?.call()
|
||||
|
||||
page = (await browser.pages())[0]
|
||||
//set cookies
|
||||
hooks.cookieWillSet?.call(bossCookies)
|
||||
@@ -782,6 +1118,13 @@ export async function mainLoop (hooks) {
|
||||
}
|
||||
await setDomainLocalStorage(browser, localStoragePageUrl, bossLocalStorage)
|
||||
await page.bringToFront()
|
||||
await hooks.mainFlowWillLaunch?.callAsync({
|
||||
jobNotMatchStrategy,
|
||||
jobNotActiveStrategy,
|
||||
expectCityNotMatchStrategy,
|
||||
blockJobNotSuit,
|
||||
blockBossNotActive,
|
||||
})
|
||||
await toRecommendPage(hooks)
|
||||
// goto search
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
runtimeFolderPath,
|
||||
ensureEditThisCookie,
|
||||
editThisCookieExtensionPath,
|
||||
isRunFromUi,
|
||||
} from './utils.mjs'
|
||||
|
||||
import { EventEmitter } from 'node:events'
|
||||
@@ -56,10 +55,8 @@ export async function main() {
|
||||
|
||||
page.once('close', async () => {
|
||||
browser.close()
|
||||
if (isRunFromUi) {
|
||||
const electron = await import('electron')
|
||||
electron.app.quit()
|
||||
}
|
||||
const electron = await import('electron')
|
||||
electron.app.quit()
|
||||
})
|
||||
|
||||
const { dispose: disposeNavigationLock } = await blockNavigation(page, (req) => !req.url().startsWith('https://www.zhipin.com'))
|
||||
|
||||
@@ -4,7 +4,6 @@ import os from 'node:os'
|
||||
import extractZip from 'extract-zip'
|
||||
import packageJson from './package.json' assert {type: 'json'}
|
||||
|
||||
export const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE)
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
export const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
|
||||
@@ -28,12 +27,8 @@ async function getEditThisCookieZipPath () {
|
||||
if (editThisCookieZipPath) {
|
||||
return editThisCookieZipPath
|
||||
}
|
||||
if (isRunFromUi) {
|
||||
const { app } = await import('electron')
|
||||
editThisCookieZipPath = path.join(app.getAppPath(), './node_modules', packageJson.name, 'extensions', 'EditThisCookie.zip')
|
||||
} else {
|
||||
editThisCookieZipPath = path.join(__dirname, 'extensions', 'EditThisCookie.zip')
|
||||
}
|
||||
const { app } = await import('electron')
|
||||
editThisCookieZipPath = path.join(app.getAppPath(), './node_modules', packageJson.name, 'extensions', 'EditThisCookie.zip')
|
||||
return editThisCookieZipPath
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ const main = async () => {
|
||||
pageLoaded: new SyncHook(),
|
||||
cookieWillSet: new SyncHook(['cookies']),
|
||||
userInfoResponse: new AsyncSeriesHook(['userInfo']),
|
||||
mainFlowWillLaunch: new AsyncSeriesHook(['args']),
|
||||
newChatWillStartup: new AsyncSeriesHook(['positionInfoDetail']),
|
||||
newChatStartup: new AsyncSeriesHook(['positionInfoDetail', 'chatRunningContext']),
|
||||
noPositionFoundForCurrentJob: new SyncHook(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@geekgeekrun/sqlite-plugin",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MarkAsNotSuitReason } from "../enums";
|
||||
import { MarkAsNotSuitOp, MarkAsNotSuitReason } from "../enums";
|
||||
import { requireTypeorm } from "../utils/module-loader";
|
||||
import { ChatStartupFrom } from "./ChatStartupLog";
|
||||
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm()
|
||||
@@ -27,6 +27,11 @@ export class MarkAsNotSuitLog {
|
||||
})
|
||||
markReason?: MarkAsNotSuitReason
|
||||
|
||||
@Column({
|
||||
nullable: true
|
||||
})
|
||||
markOp?: MarkAsNotSuitOp
|
||||
|
||||
@Column({
|
||||
nullable: true
|
||||
})
|
||||
|
||||
@@ -3,4 +3,23 @@ export enum MarkAsNotSuitReason {
|
||||
BOSS_INACTIVE = 1,
|
||||
USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON = 2,
|
||||
JOB_NOT_SUIT = 3,
|
||||
JOB_CITY_NOT_SUIT = 4,
|
||||
JOB_WORK_EXP_NOT_SUIT = 5,
|
||||
JOB_SALARY_NOT_SUIT = 6,
|
||||
}
|
||||
|
||||
export enum MarkAsNotSuitOp {
|
||||
MARK_AS_NOT_SUIT_ON_BOSS = 1,
|
||||
MARK_AS_NOT_SUIT_ON_LOCAL = 2,
|
||||
NO_OP = 3
|
||||
}
|
||||
|
||||
export enum StrategyScopeOptionWhenMarkJobNotMatch {
|
||||
ALL_JOB = 1,
|
||||
ONLY_COMPANY_MATCHED_JOB = 2
|
||||
}
|
||||
|
||||
export enum SalaryCalculateWay {
|
||||
MONTH_SALARY = 1,
|
||||
ANNUAL_PACKAGE = 2,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import { DataSource, Raw } from "typeorm";
|
||||
import { BossActiveStatusRecord } from "./entity/BossActiveStatusRecord";
|
||||
import { BossInfo } from "./entity/BossInfo";
|
||||
import { CompanyInfo } from "./entity/CompanyInfo";
|
||||
@@ -270,7 +270,7 @@ export async function saveMarkAsNotSuitRecord(
|
||||
ds: DataSource,
|
||||
_jobInfo,
|
||||
{ encryptUserId },
|
||||
{ autoStartupChatRecordId = undefined, markFrom = undefined, extInfo = undefined, markReason = undefined } = {}
|
||||
{ autoStartupChatRecordId = undefined, markFrom = undefined, extInfo = undefined, markReason = undefined, markOp = undefined } = {}
|
||||
) {
|
||||
const { jobInfo } = _jobInfo;
|
||||
|
||||
@@ -283,7 +283,8 @@ export async function saveMarkAsNotSuitRecord(
|
||||
autoStartupChatRecordId,
|
||||
markFrom,
|
||||
markReason,
|
||||
extInfo: extInfo ? JSON.stringify(extInfo) : undefined
|
||||
extInfo: extInfo ? JSON.stringify(extInfo) : undefined,
|
||||
markOp
|
||||
}
|
||||
Object.assign(markAsNotSuitLog, markAsNotSuitLogPayload)
|
||||
|
||||
@@ -326,3 +327,15 @@ export async function saveGptCompletionRequestRecord(
|
||||
//#endregion
|
||||
return
|
||||
}
|
||||
|
||||
export async function getNotSuitMarkRecordsInLastSomeDays (ds: DataSource, days = 0) {
|
||||
const repo = ds.getRepository(MarkAsNotSuitLog)
|
||||
const result = await repo.findBy({
|
||||
date: Raw(alias => `DATE(${alias}) >= DATE('${
|
||||
new Date(
|
||||
Number(new Date()) - 7 * 24 * 60 * 60 * 1000
|
||||
).toISOString()
|
||||
}')`)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "reflect-metadata";
|
||||
import { type DataSource } from "typeorm";
|
||||
import { requireTypeorm } from "./utils/module-loader";
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { BossInfo } from "./entity/BossInfo";
|
||||
import { BossInfoChangeLog } from "./entity/BossInfoChangeLog";
|
||||
@@ -22,16 +23,19 @@ import { ChatMessageRecord } from './entity/ChatMessageRecord'
|
||||
import { LlmModelUsageRecord } from './entity/LlmModelUsageRecord'
|
||||
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { saveChatStartupRecord, saveJobInfoFromRecommendPage, saveMarkAsNotSuitRecord } from "./handlers";
|
||||
import { saveChatStartupRecord, saveJobInfoFromRecommendPage, saveMarkAsNotSuitRecord, getNotSuitMarkRecordsInLastSomeDays } from "./handlers";
|
||||
import { UpdateChatStartupLogTable1729182577167 } from "./migrations/1729182577167-UpdateChatStartupLogTable";
|
||||
import minimist from 'minimist'
|
||||
import { UpdateBossInfoTable1732032381304 } from "./migrations/1732032381304-UpdateBossInfoTable";
|
||||
import { MarkAsNotSuitOp, MarkAsNotSuitReason } from "./enums";
|
||||
import { AddColumnForMarkAsNotSuitLog1746092370665 } from "./migrations/1746092370665-AddColumnForMarkAsNotSuitLog";
|
||||
import { Init1000000000000 } from "./migrations/1000000000000-Init";
|
||||
|
||||
export function initDb(dbFilePath) {
|
||||
const { DataSource } = requireTypeorm()
|
||||
const appDataSource = new DataSource({
|
||||
type: "sqlite",
|
||||
synchronize: true,
|
||||
synchronize: false,
|
||||
logging: true,
|
||||
logger: "simple-console",
|
||||
database: dbFilePath,
|
||||
@@ -57,8 +61,10 @@ export function initDb(dbFilePath) {
|
||||
LlmModelUsageRecord,
|
||||
],
|
||||
migrations: [
|
||||
Init1000000000000,
|
||||
UpdateChatStartupLogTable1729182577167,
|
||||
UpdateBossInfoTable1732032381304,
|
||||
AddColumnForMarkAsNotSuitLog1746092370665,
|
||||
],
|
||||
migrationsRun: true
|
||||
});
|
||||
@@ -97,6 +103,60 @@ export default class SqlitePlugin {
|
||||
return await userInfoRepository.save(user);
|
||||
}
|
||||
);
|
||||
hooks.mainFlowWillLaunch.tapPromise(
|
||||
"SqlitePlugin",
|
||||
async ({
|
||||
jobNotMatchStrategy,
|
||||
jobNotActiveStrategy,
|
||||
expectCityNotMatchStrategy,
|
||||
blockJobNotSuit,
|
||||
blockBossNotActive,
|
||||
}) => {
|
||||
if (
|
||||
jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL ||
|
||||
jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL ||
|
||||
expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
|
||||
) {
|
||||
const ds = await this.initPromise;
|
||||
const last7DayMarkRecords = (await getNotSuitMarkRecordsInLastSomeDays(ds, 7) ?? []);
|
||||
if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
last7DayMarkRecords
|
||||
.filter(it =>
|
||||
[
|
||||
MarkAsNotSuitReason.JOB_NOT_SUIT,
|
||||
MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON
|
||||
].includes(it.markReason)
|
||||
)
|
||||
.map(
|
||||
it => it.encryptJobId
|
||||
)
|
||||
.forEach(
|
||||
id => blockJobNotSuit.add(id)
|
||||
)
|
||||
}
|
||||
if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
last7DayMarkRecords
|
||||
.filter(it => it.markReason === MarkAsNotSuitReason.BOSS_INACTIVE)
|
||||
.map(
|
||||
it => it.encryptJobId
|
||||
)
|
||||
.forEach(
|
||||
id => blockJobNotSuit.add(id)
|
||||
)
|
||||
}
|
||||
if (expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
|
||||
last7DayMarkRecords
|
||||
.filter(it => it.markReason === MarkAsNotSuitReason.JOB_CITY_NOT_SUIT)
|
||||
.map(
|
||||
it => it.encryptJobId
|
||||
)
|
||||
.forEach(
|
||||
id => blockJobNotSuit.add(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
hooks.jobDetailIsGetFromRecommendList.tapPromise("SqlitePlugin", async (_jobInfo) => {
|
||||
const ds = await this.initPromise;
|
||||
@@ -111,13 +171,14 @@ export default class SqlitePlugin {
|
||||
});
|
||||
});
|
||||
|
||||
hooks.jobMarkedAsNotSuit.tapPromise("SqlitePlugin", async (_jobInfo, { markFrom = ChatStartupFrom.AutoFromRecommendList, markReason = undefined, extInfo = undefined } = {}) => {
|
||||
hooks.jobMarkedAsNotSuit.tapPromise("SqlitePlugin", async (_jobInfo, { markFrom = ChatStartupFrom.AutoFromRecommendList, markReason = undefined, extInfo = undefined, markOp = undefined } = {}) => {
|
||||
const ds = await this.initPromise;
|
||||
return await saveMarkAsNotSuitRecord(ds, _jobInfo, this.userInfo, {
|
||||
autoStartupChatRecordId: this.runRecordId,
|
||||
markFrom,
|
||||
markReason,
|
||||
extInfo
|
||||
extInfo,
|
||||
markOp
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
24
packages/sqlite-plugin/src/migrations/1000000000000-Init.ts
Normal file
24
packages/sqlite-plugin/src/migrations/1000000000000-Init.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DataSource, MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
export class Init1000000000000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const tableSchemaStatements = `CREATE TABLE IF NOT EXISTS "boss_info" ("encryptBossId" varchar PRIMARY KEY NOT NULL, "encryptCompanyId" varchar, "name" varchar NOT NULL, "date" datetime NOT NULL, "title" varchar NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "boss_info_change_log" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptBossId" varchar NOT NULL, "updateTime" datetime NOT NULL, "dataAsJson" varchar NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "chat_startup_log" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptJobId" varchar NOT NULL, "encryptCurrentUserId" varchar NOT NULL, "date" datetime NOT NULL, "chatStartupFrom" integer, "autoStartupChatRecordId" integer);
|
||||
CREATE TABLE IF NOT EXISTS "company_info_change_log" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptCompanyId" varchar NOT NULL, "updateTime" datetime NOT NULL, "dataAsJson" varchar NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "company_info" ("encryptCompanyId" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "brandName" varchar NOT NULL, "scaleLow" integer, "scaleHigh" integer, "stageName" varchar, "industryName" varchar);
|
||||
CREATE TABLE IF NOT EXISTS "job_info" ("encryptJobId" varchar PRIMARY KEY NOT NULL, "jobName" varchar NOT NULL, "positionName" varchar NOT NULL, "salaryLow" integer, "salaryHigh" integer, "salaryMonth" integer, "experienceName" varchar NOT NULL, "publishDate" datetime, "degreeName" varchar, "address" varchar, "description" varchar NOT NULL, "encryptBossId" varchar NOT NULL, "encryptCompanyId" varchar NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "job_info_change_log" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptJobId" varchar NOT NULL, "updateTime" datetime NOT NULL, "dataAsJson" varchar NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "boss_active_status_record" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptBossId" varchar NOT NULL, "lastActiveStatus" varchar, "updateTime" datetime NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "user_info" ("encryptUserId" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "auto_start_chat_run_record" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "date" datetime NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS "mark_as_not_suit_log" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptJobId" varchar NOT NULL, "encryptCurrentUserId" varchar NOT NULL, "date" datetime NOT NULL, "markFrom" integer, "markReason" integer, "extInfo" varchar, "autoStartupChatRecordId" integer);
|
||||
CREATE TABLE IF NOT EXISTS "chat_message_record" ("mid" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "encryptFromUserId" varchar NOT NULL, "encryptToUserId" varchar NOT NULL, "time" datetime, "type" varchar, "style" varchar, "text" varchar, "imageUrl" varchar, "imageWidth" integer, "imageHeight" integer);
|
||||
CREATE TABLE IF NOT EXISTS "llm_model_usage_record" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "providerCompleteApiUrl" varchar NOT NULL, "model" varchar NOT NULL, "providerApiSecret" varchar, "completionTokens" integer, "promptTokens" integer, "promptCacheHitTokens" integer, "promptCacheMissTokens" integer, "totalTokens" integer, "requestStartTime" datetime NOT NULL, "requestEndTime" datetime, "hasError" boolean NOT NULL, "errorMessage" varchar NOT NULL, "requestScene" integer);`.split('\n')
|
||||
for(const statement of tableSchemaStatements) {
|
||||
await queryRunner.query(statement);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,24 @@
|
||||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
import { DataSource, MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
import { VBossLibrary } from "../entity/VBossLibrary";
|
||||
import { VChatStartupLog } from "../entity/VChatStartupLog";
|
||||
import { VCompanyLibrary } from "../entity/VCompanyLibrary";
|
||||
import { VJobLibrary } from "../entity/VJobLibrary";
|
||||
import { VMarkAsNotSuitLog } from "../entity/VMarkAsNotSuitLog";
|
||||
|
||||
const viewNames = [
|
||||
"v_boss_library",
|
||||
"v_chat_startup_log",
|
||||
"v_company_library",
|
||||
"v_job_library",
|
||||
"v_mark_as_not_suit_log"
|
||||
];
|
||||
const ViewEntities = [
|
||||
VBossLibrary,
|
||||
VChatStartupLog,
|
||||
VCompanyLibrary,
|
||||
VJobLibrary,
|
||||
VMarkAsNotSuitLog,
|
||||
]
|
||||
|
||||
export class UpdateChatStartupLogTable1729182577167
|
||||
implements MigrationInterface
|
||||
{
|
||||
export class UpdateChatStartupLogTable1729182577167 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const viewName of viewNames) {
|
||||
await queryRunner.query(`DROP VIEW IF EXISTS "${viewName}"`);
|
||||
for (const EntityDefinition of ViewEntities) {
|
||||
const dataSource = queryRunner.connection as DataSource;
|
||||
const viewMetadata = dataSource.getMetadata(EntityDefinition);
|
||||
await queryRunner.query(`DROP VIEW IF EXISTS "${viewMetadata.tableName}"`);
|
||||
}
|
||||
if (await queryRunner.hasTable("boss_active_status_record")) {
|
||||
if (await queryRunner.hasColumn("boss_active_status_record", "updateDate")) {
|
||||
@@ -40,14 +45,17 @@ export class UpdateChatStartupLogTable1729182577167
|
||||
})
|
||||
)
|
||||
}
|
||||
for (const EntityDefinition of ViewEntities) {
|
||||
const dataSource = queryRunner.connection as DataSource;
|
||||
const viewMetadata = dataSource.getMetadata(EntityDefinition);
|
||||
let expression = viewMetadata.expression;
|
||||
if (typeof expression === 'function') {
|
||||
expression = expression(dataSource).getQuery();
|
||||
}
|
||||
await queryRunner.query(`CREATE VIEW "${viewMetadata.tableName}" AS ${expression}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const viewName of viewNames) {
|
||||
await queryRunner.query(`DROP VIEW IF EXISTS "${viewName}"`);
|
||||
}
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE boss_active_status_record RENAME COLUMN updateTime TO updateDate`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
import { DataSource, MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
import { VBossLibrary } from "../entity/VBossLibrary";
|
||||
import { VChatStartupLog } from "../entity/VChatStartupLog";
|
||||
import { VCompanyLibrary } from "../entity/VCompanyLibrary";
|
||||
import { VJobLibrary } from "../entity/VJobLibrary";
|
||||
import { VMarkAsNotSuitLog } from "../entity/VMarkAsNotSuitLog";
|
||||
|
||||
const viewNames = [
|
||||
"v_boss_library",
|
||||
"v_chat_startup_log",
|
||||
"v_company_library",
|
||||
"v_job_library",
|
||||
"v_mark_as_not_suit_log",
|
||||
];
|
||||
const ViewEntities = [
|
||||
VBossLibrary,
|
||||
VChatStartupLog,
|
||||
VCompanyLibrary,
|
||||
VJobLibrary,
|
||||
VMarkAsNotSuitLog,
|
||||
]
|
||||
|
||||
export class UpdateBossInfoTable1732032381304 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const viewName of viewNames) {
|
||||
await queryRunner.query(`DROP VIEW IF EXISTS "${viewName}"`);
|
||||
for (const EntityDefinition of ViewEntities) {
|
||||
const dataSource = queryRunner.connection as DataSource;
|
||||
const viewMetadata = dataSource.getMetadata(EntityDefinition);
|
||||
await queryRunner.query(`DROP VIEW IF EXISTS "${viewMetadata.tableName}"`);
|
||||
}
|
||||
if (await queryRunner.hasTable("boss_info")) {
|
||||
if (await queryRunner.hasColumn("boss_info", "encryptCompanyId")) {
|
||||
@@ -26,6 +33,15 @@ export class UpdateBossInfoTable1732032381304 implements MigrationInterface {
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const EntityDefinition of ViewEntities) {
|
||||
const dataSource = queryRunner.connection as DataSource;
|
||||
const viewMetadata = dataSource.getMetadata(EntityDefinition);
|
||||
let expression = viewMetadata.expression;
|
||||
if (typeof expression === 'function') {
|
||||
expression = expression(dataSource).getQuery();
|
||||
}
|
||||
await queryRunner.query(`CREATE VIEW "${viewMetadata.tableName}" AS ${expression}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { DataSource, MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
import { VBossLibrary } from "../entity/VBossLibrary";
|
||||
import { VChatStartupLog } from "../entity/VChatStartupLog";
|
||||
import { VCompanyLibrary } from "../entity/VCompanyLibrary";
|
||||
import { VJobLibrary } from "../entity/VJobLibrary";
|
||||
import { VMarkAsNotSuitLog } from "../entity/VMarkAsNotSuitLog";
|
||||
|
||||
const ViewEntities = [
|
||||
VBossLibrary,
|
||||
VChatStartupLog,
|
||||
VCompanyLibrary,
|
||||
VJobLibrary,
|
||||
VMarkAsNotSuitLog,
|
||||
]
|
||||
|
||||
export class AddColumnForMarkAsNotSuitLog1746092370665 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const EntityDefinition of ViewEntities) {
|
||||
const dataSource = queryRunner.connection as DataSource;
|
||||
const viewMetadata = dataSource.getMetadata(EntityDefinition);
|
||||
await queryRunner.query(`DROP VIEW IF EXISTS "${viewMetadata.tableName}"`);
|
||||
}
|
||||
if (await queryRunner.hasTable("mark_as_not_suit_log")) {
|
||||
if (!await queryRunner.hasColumn("mark_as_not_suit_log", "markOp")) {
|
||||
await queryRunner.addColumn(
|
||||
"mark_as_not_suit_log",
|
||||
new TableColumn({
|
||||
name: "markOp",
|
||||
type: "number",
|
||||
isNullable: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const EntityDefinition of ViewEntities) {
|
||||
const dataSource = queryRunner.connection as DataSource;
|
||||
const viewMetadata = dataSource.getMetadata(EntityDefinition);
|
||||
let expression = viewMetadata.expression;
|
||||
if (typeof expression === 'function') {
|
||||
expression = expression(dataSource).getQuery();
|
||||
}
|
||||
await queryRunner.query(`CREATE VIEW "${viewMetadata.tableName}" AS ${expression}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
import * as path from 'node:path';
|
||||
const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE)
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
export function requireTypeorm () {
|
||||
const importResult = require('typeorm')
|
||||
return importResult
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "geekgeekrun-ui",
|
||||
"version": "0.4.0",
|
||||
"version": "0.8.0",
|
||||
"description": "Boss 炸弹 - 自动开聊Boss,助力每位打工人求职!",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "geekgeekrun",
|
||||
@@ -39,6 +39,7 @@
|
||||
"diff": "^7.0.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"minimist": "^1.2.8",
|
||||
"mitt": "^3.0.1",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"pinia": "^3.0.2",
|
||||
"puppeteer": "20.1.0",
|
||||
@@ -65,7 +66,7 @@
|
||||
"electron": "28.2.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-vite": "^2.0.0",
|
||||
"element-plus": "^2.5.5",
|
||||
"element-plus": "^2.8.4",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-vue": "^9.20.1",
|
||||
"find-chrome-bin": "^2.0.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "0.4.0",
|
||||
"buildVersion": 9,
|
||||
"buildTime": 1745903517984,
|
||||
"buildHash": "25e52530228e78c3cf78caa053c7da6bb138e5e2",
|
||||
"version": "0.8.0",
|
||||
"buildVersion": 15,
|
||||
"buildTime": 1749349412239,
|
||||
"buildHash": "9b64f05cb6205fc89bd3ad1c08a59993cd6ac127",
|
||||
"name": "geekgeekrun-ui"
|
||||
}
|
||||
7492
packages/ui/src/common/constant/cityGroup.json
Normal file
7492
packages/ui/src/common/constant/cityGroup.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,13 @@ export const getLastUsedAndAvailableBrowser = async (): Promise<BrowserInfo | nu
|
||||
await removeLastUsedAndAvailableBrowserPath()
|
||||
return null
|
||||
}
|
||||
|
||||
// blacklist browser
|
||||
if (path.includes(`Microsoft\\Edge\\Application\\msedge.exe`)) {
|
||||
await removeLastUsedAndAvailableBrowserPath()
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
executablePath: path,
|
||||
browser
|
||||
|
||||
@@ -8,12 +8,10 @@ import {
|
||||
getLastUsedAndAvailableBrowser,
|
||||
removeLastUsedAndAvailableBrowserPath
|
||||
} from '../browser-history'
|
||||
import { getExecutableFileVersion } from '@geekgeekrun/utils/windows-only/file.mjs'
|
||||
import gtag from '../../../../utils/gtag'
|
||||
|
||||
const getPuppeteerManagerModule = async () => {
|
||||
let puppeteerManager
|
||||
puppeteerManager = await import('@puppeteer/browsers')
|
||||
const puppeteerManager = await import('@puppeteer/browsers')
|
||||
|
||||
return puppeteerManager
|
||||
}
|
||||
@@ -76,7 +74,8 @@ export const checkAndDownloadPuppeteerExecutable = async (
|
||||
browser: puppeteerManager.Browser.CHROME,
|
||||
cacheDir,
|
||||
buildId: EXPECT_CHROMIUM_BUILD_ID,
|
||||
downloadProgressCallback: options.downloadProgressCallback
|
||||
downloadProgressCallback: options.downloadProgressCallback,
|
||||
baseUrl: `https://registry.npmmirror.com/-/binary/chrome-for-testing`
|
||||
})
|
||||
} else {
|
||||
gtag('use_installed_browser')
|
||||
@@ -128,33 +127,8 @@ export const getAnyAvailablePuppeteerExecutable = async (): Promise<BrowserInfo
|
||||
|
||||
export async function findAndLocateUserInstalledChromiumExecutableSync(): Promise<BrowserInfo> {
|
||||
const exceptChromiumMainVersion = Number(EXPECT_CHROMIUM_BUILD_ID.split('.')[0])
|
||||
// For windows, try to find Edge(chromium)
|
||||
if (os.platform() === 'win32') {
|
||||
// TODO: handle windows
|
||||
const edgeExecutableLocation = path.join(
|
||||
process.env['ProgramFiles(x86)']!,
|
||||
'Microsoft/Edge/Application',
|
||||
'msedge.exe'
|
||||
)
|
||||
if (fs.existsSync(edgeExecutableLocation)) {
|
||||
try {
|
||||
const version = await getExecutableFileVersion(edgeExecutableLocation)
|
||||
const mainVersion = Number(version.split('.')[0])
|
||||
if (mainVersion >= exceptChromiumMainVersion) {
|
||||
return {
|
||||
executablePath: edgeExecutableLocation,
|
||||
browser: `Edge ${version}`
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For other, use findChrome
|
||||
let findChrome: typeof import('find-chrome-bin').findChrome
|
||||
findChrome = (await import('find-chrome-bin')).findChrome
|
||||
const findChrome: typeof import('find-chrome-bin').findChrome = (await import('find-chrome-bin'))
|
||||
.findChrome
|
||||
const targetBrowser = await findChrome({
|
||||
min: exceptChromiumMainVersion
|
||||
})
|
||||
|
||||
@@ -25,14 +25,10 @@ function runWithDaemon({ runRecordId, runMode, parentProcessPipe }) {
|
||||
const subProcessOfCore = childProcess.spawn(
|
||||
process.argv[0],
|
||||
isUiDev
|
||||
? [...process.argv.slice(1), `--run-record-id=${runRecordId}`]
|
||||
: [`--run-record-id=${runRecordId}`],
|
||||
? [process.argv[1], `--run-record-id=${runRecordId}`, `--mode=${runMode}`]
|
||||
: [`--run-record-id=${runRecordId}`, `--mode=${runMode}`],
|
||||
{
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc'],
|
||||
env: {
|
||||
...process.env,
|
||||
MAIN_BOSSGEEKGO_UI_RUN_MODE: runMode
|
||||
}
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ const runAutoChat = async () => {
|
||||
pageLoaded: new SyncHook(),
|
||||
cookieWillSet: new SyncHook(['cookies']),
|
||||
userInfoResponse: new AsyncSeriesHook(['userInfo']),
|
||||
mainFlowWillLaunch: new AsyncSeriesHook(['args']),
|
||||
jobDetailIsGetFromRecommendList: new AsyncSeriesHook(['userInfo']),
|
||||
newChatWillStartup: new AsyncSeriesHook(['positionInfoDetail']),
|
||||
newChatStartup: new AsyncSeriesHook(['positionInfoDetail', 'chatRunningContext']),
|
||||
|
||||
@@ -92,7 +92,11 @@ const attachRequestsListener = async (target: Target) => {
|
||||
},
|
||||
markReason: MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON
|
||||
}
|
||||
gtag('job_marked_as_not_suit', { markFrom: markDetail.markFrom })
|
||||
gtag('job_marked_as_not_suit', {
|
||||
markFrom: markDetail.markFrom,
|
||||
bossActiveTimeDesc: currentJobData?.bossInfo?.activeTimeDesc,
|
||||
encryptJobId: currentJobData?.jobInfo?.encryptId
|
||||
})
|
||||
if (reasonCodeToTextMap[chosenCode]?.includes('活跃度低')) {
|
||||
markDetail.markReason = MarkAsNotSuitReason.BOSS_INACTIVE
|
||||
markDetail.extInfo.bossActiveTimeDesc = currentJobData?.bossInfo.activeTimeDesc
|
||||
@@ -127,7 +131,10 @@ const attachRequestsListener = async (target: Target) => {
|
||||
const currentUserInfo = await page.evaluate(
|
||||
'document.querySelector(".job-detail-box").__vue__.$store.state.userInfo'
|
||||
)
|
||||
gtag('new_chat_startup', { chatStartupFrom: ChatStartupFrom.ManuallyFromRecommendList })
|
||||
gtag('new_chat_startup', {
|
||||
chatStartupFrom: ChatStartupFrom.ManuallyFromRecommendList,
|
||||
encryptJobId: currentJobData?.jobInfo?.encryptId
|
||||
})
|
||||
await saveChatStartupRecord(
|
||||
await dbInitPromise,
|
||||
currentJobData,
|
||||
|
||||
@@ -95,9 +95,61 @@ export default function initIpc() {
|
||||
if (hasOwn(payload, 'expectJobDescRegExpStr')) {
|
||||
bossConfig.expectJobDescRegExpStr = payload.expectJobDescRegExpStr
|
||||
}
|
||||
if (hasOwn(payload, 'jobNotMatchStrategy')) {
|
||||
bossConfig.jobNotMatchStrategy = payload.jobNotMatchStrategy
|
||||
}
|
||||
if (hasOwn(payload, 'markAsNotActiveSelectedTimeRange')) {
|
||||
bossConfig.markAsNotActiveSelectedTimeRange = payload.markAsNotActiveSelectedTimeRange
|
||||
}
|
||||
if (hasOwn(payload, 'jobNotActiveStrategy')) {
|
||||
bossConfig.jobNotActiveStrategy = payload.jobNotActiveStrategy
|
||||
}
|
||||
if (hasOwn(payload, 'autoReminder')) {
|
||||
bossConfig.autoReminder = payload.autoReminder
|
||||
}
|
||||
|
||||
// city
|
||||
if (hasOwn(payload, 'expectCityList')) {
|
||||
bossConfig.expectCityList = payload.expectCityList
|
||||
}
|
||||
if (hasOwn(payload, 'expectCityNotMatchStrategy')) {
|
||||
bossConfig.expectCityNotMatchStrategy = payload.expectCityNotMatchStrategy
|
||||
}
|
||||
if (hasOwn(payload, 'strategyScopeOptionWhenMarkJobCityNotMatch')) {
|
||||
bossConfig.strategyScopeOptionWhenMarkJobCityNotMatch =
|
||||
payload.strategyScopeOptionWhenMarkJobCityNotMatch
|
||||
}
|
||||
|
||||
// salary
|
||||
if (hasOwn(payload, 'expectSalaryCalculateWay')) {
|
||||
bossConfig.expectSalaryCalculateWay = payload.expectSalaryCalculateWay
|
||||
}
|
||||
if (hasOwn(payload, 'expectSalaryNotMatchStrategy')) {
|
||||
bossConfig.expectSalaryNotMatchStrategy = payload.expectSalaryNotMatchStrategy
|
||||
}
|
||||
if (hasOwn(payload, 'strategyScopeOptionWhenMarkSalaryNotMatch')) {
|
||||
bossConfig.strategyScopeOptionWhenMarkSalaryNotMatch =
|
||||
payload.strategyScopeOptionWhenMarkSalaryNotMatch
|
||||
}
|
||||
if (hasOwn(payload, 'expectSalaryLow')) {
|
||||
bossConfig.expectSalaryLow = payload.expectSalaryLow
|
||||
}
|
||||
if (hasOwn(payload, 'expectSalaryHigh')) {
|
||||
bossConfig.expectSalaryHigh = payload.expectSalaryHigh
|
||||
}
|
||||
|
||||
// work exp
|
||||
if (hasOwn(payload, 'expectWorkExpList')) {
|
||||
bossConfig.expectWorkExpList = payload.expectWorkExpList
|
||||
}
|
||||
if (hasOwn(payload, 'expectWorkExpNotMatchStrategy')) {
|
||||
bossConfig.expectWorkExpNotMatchStrategy = payload.expectWorkExpNotMatchStrategy
|
||||
}
|
||||
if (hasOwn(payload, 'strategyScopeOptionWhenMarkJobWorkExpNotMatch')) {
|
||||
bossConfig.strategyScopeOptionWhenMarkJobWorkExpNotMatch =
|
||||
payload.strategyScopeOptionWhenMarkJobWorkExpNotMatch
|
||||
}
|
||||
|
||||
promiseArr.push(writeConfigFile('boss.json', bossConfig))
|
||||
|
||||
if (hasOwn(payload, 'expectCompanies')) {
|
||||
@@ -142,12 +194,15 @@ export default function initIpc() {
|
||||
}
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'geekAutoStartWithBossDaemon',
|
||||
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath
|
||||
}
|
||||
subProcessOfPuppeteer = childProcess.spawn(
|
||||
process.argv[0],
|
||||
[...process.argv.slice(1), '--mode-to-daemon=geekAutoStartWithBossMain'],
|
||||
[
|
||||
process.argv[1],
|
||||
`--mode=geekAutoStartWithBossDaemon`,
|
||||
`--mode-to-daemon=geekAutoStartWithBossMain`
|
||||
],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
|
||||
@@ -196,13 +251,16 @@ export default function initIpc() {
|
||||
}
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'readNoReplyAutoReminder',
|
||||
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath
|
||||
}
|
||||
subProcessOfPuppeteer = childProcess.spawn(process.argv[0], process.argv.slice(1), {
|
||||
env: subProcessEnv,
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
|
||||
})
|
||||
subProcessOfPuppeteer = childProcess.spawn(
|
||||
process.argv[0],
|
||||
[process.argv[1], `--mode=readNoReplyAutoReminder`],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
|
||||
}
|
||||
)
|
||||
// console.log(subProcessOfPuppeteer)
|
||||
return new Promise((resolve, reject) => {
|
||||
subProcessOfPuppeteer!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => {
|
||||
@@ -255,15 +313,10 @@ export default function initIpc() {
|
||||
if (subProcessOfCheckAndDownloadDependencies) {
|
||||
return
|
||||
}
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'checkAndDownloadDependenciesForInit'
|
||||
}
|
||||
subProcessOfCheckAndDownloadDependencies = childProcess.spawn(
|
||||
process.argv[0],
|
||||
process.argv.slice(1),
|
||||
[process.argv[1], `--mode=checkAndDownloadDependenciesForInit`],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: [null, null, null, 'pipe', 'ipc']
|
||||
}
|
||||
)
|
||||
@@ -325,12 +378,11 @@ export default function initIpc() {
|
||||
}
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'launchBossZhipinLoginPageWithPreloadExtension',
|
||||
PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath
|
||||
}
|
||||
subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn(
|
||||
process.argv[0],
|
||||
process.argv.slice(1),
|
||||
[process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: [null, null, null, 'pipe', 'ipc']
|
||||
@@ -406,13 +458,16 @@ export default function initIpc() {
|
||||
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'launchBossSite',
|
||||
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable!.executablePath
|
||||
}
|
||||
subProcessOfOpenBossSite = childProcess.spawn(process.argv[0], process.argv.slice(1), {
|
||||
env: subProcessEnv,
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe']
|
||||
})
|
||||
subProcessOfOpenBossSite = childProcess.spawn(
|
||||
process.argv[0],
|
||||
[process.argv[1], `--mode=launchBossSite`],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe']
|
||||
}
|
||||
)
|
||||
subProcessOfOpenBossSite.once('exit', () => {
|
||||
subProcessOfOpenBossSiteDefer = null
|
||||
})
|
||||
@@ -501,16 +556,15 @@ export default function initIpc() {
|
||||
ipcMain.handle('save-resume-content', saveResumeHandler)
|
||||
resumeEditorWindow?.once('closed', () => {
|
||||
ipcMain.removeHandler('save-resume-content')
|
||||
ipcMain.removeHandler('fetch-resume-content')
|
||||
defer.reject(new Error('cancel'))
|
||||
})
|
||||
|
||||
ipcMain.handle('fetch-resume-content', async () => {
|
||||
const res = (await readConfigFile('resumes.json'))?.[0]
|
||||
return res?.content ?? null
|
||||
})
|
||||
return defer.promise
|
||||
})
|
||||
ipcMain.handle('fetch-resume-content', async () => {
|
||||
const res = (await readConfigFile('resumes.json'))?.[0]
|
||||
return res?.content ?? null
|
||||
})
|
||||
ipcMain.on('no-reply-reminder-prompt-edit', async () => {
|
||||
const template = await readStorageFile(autoReminderPromptTemplateFileName, { isJson: false })
|
||||
if (!template) {
|
||||
|
||||
@@ -32,6 +32,10 @@ const rechatLlmFallback =
|
||||
readConfigFile('boss.json').autoReminder?.rechatLlmFallback ??
|
||||
RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION
|
||||
|
||||
const expectJobTypeRegExpStr = readConfigFile('boss.json').expectJobTypeRegExpStr
|
||||
const onlyRemindBossWithExpectJobType =
|
||||
readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ?? !!expectJobTypeRegExpStr
|
||||
|
||||
const dbInitPromise = initDb(getPublicDbFilePath())
|
||||
|
||||
export const pageMapByName: {
|
||||
@@ -117,7 +121,6 @@ const mainLoop = async () => {
|
||||
'你与该职位竞争者PK情况',
|
||||
'简历诊断提醒',
|
||||
'附件简历还没准备好',
|
||||
'开场问题,期待你的回答',
|
||||
'设置合适的期望薪资范围'
|
||||
].map((it) => new RegExp(it))
|
||||
browser = await bootstrap()
|
||||
@@ -198,9 +201,10 @@ const mainLoop = async () => {
|
||||
(rechatLimitDay && it.updateTime
|
||||
? +new Date() - it.updateTime < rechatLimitDay * 24 * 60 * 60 * 1000
|
||||
: true) &&
|
||||
((it.lastIsSelf && it.lastMsgStatus === MsgStatus.HAS_READ) ||
|
||||
((((it.lastIsSelf && it.lastMsgStatus === MsgStatus.HAS_READ) ||
|
||||
canNotConfirmIfHasReadMsgTemplateList.some((regExp) => regExp.test(it.lastText))) &&
|
||||
!it.unreadCount
|
||||
!it.unreadCount) ||
|
||||
(!it.lastIsSelf && it.lastText === '开场问题,期待你的回答'))
|
||||
)
|
||||
})
|
||||
|
||||
@@ -267,6 +271,24 @@ const mainLoop = async () => {
|
||||
})
|
||||
}
|
||||
await sleepWithRandomDelay(1500)
|
||||
// check if expect job type match
|
||||
let isExpectJobTypeMatch = true
|
||||
if (onlyRemindBossWithExpectJobType) {
|
||||
const selectedFriendInfo = await pageMapByName.boss?.evaluate(
|
||||
`document.querySelector('.chat-conversation')?.__vue__?.selectedFriend$`
|
||||
)
|
||||
if (!selectedFriendInfo) {
|
||||
isExpectJobTypeMatch = false
|
||||
} else {
|
||||
const jobType = selectedFriendInfo?.positionName
|
||||
if (!jobType) {
|
||||
isExpectJobTypeMatch = false
|
||||
} else {
|
||||
const regExp = new RegExp(expectJobTypeRegExpStr)
|
||||
isExpectJobTypeMatch = regExp.test(jobType)
|
||||
}
|
||||
}
|
||||
}
|
||||
const conversationInfo = await pageMapByName.boss?.evaluate(
|
||||
`document.querySelector('.chat-conversation .chat-im.chat-editor')?.__vue__?.conversation$`
|
||||
)
|
||||
@@ -280,6 +302,7 @@ const mainLoop = async () => {
|
||||
|
||||
const lastGeekMessageSendTime = historyMessageList.findLast((it) => it.isSelf)?.time ?? 0
|
||||
if (
|
||||
isExpectJobTypeMatch &&
|
||||
historyMessageList[historyMessageList.length - 1].isSelf &&
|
||||
historyMessageList[historyMessageList.length - 1].status === MsgStatus.HAS_READ &&
|
||||
((conversationInfo &&
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
const runMode = process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE
|
||||
import minimist from 'minimist'
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1))
|
||||
console.log(commandlineArgs)
|
||||
|
||||
const runMode = commandlineArgs['mode'];
|
||||
|
||||
;(async () => {
|
||||
switch (runMode) {
|
||||
|
||||
@@ -2,11 +2,20 @@ import gtag from '.'
|
||||
|
||||
export default class GtagPlugin {
|
||||
apply(hooks) {
|
||||
hooks.newChatStartup.tap('GtagPlugin', (_, { chatStartupFrom }) => {
|
||||
gtag('new_chat_startup', { chatStartupFrom })
|
||||
hooks.newChatStartup.tap('GtagPlugin', (jobData, { chatStartupFrom }) => {
|
||||
gtag('new_chat_startup', {
|
||||
chatStartupFrom,
|
||||
encryptJobId: jobData.jobInfo.encryptId
|
||||
})
|
||||
})
|
||||
hooks.jobMarkedAsNotSuit.tap('GtagPlugin', (_, { markFrom }) => {
|
||||
gtag('job_marked_as_not_suit', { markFrom })
|
||||
hooks.jobMarkedAsNotSuit.tap('GtagPlugin', (jobData, { markFrom, markOp, markReason }) => {
|
||||
gtag('job_marked_as_not_suit', {
|
||||
markFrom,
|
||||
markOp,
|
||||
markReason,
|
||||
bossActiveTimeDesc: jobData.bossInfo.activeTimeDesc,
|
||||
encryptJobId: jobData.jobInfo.encryptId
|
||||
})
|
||||
})
|
||||
hooks.noPositionFoundForCurrentJob.tap('GtagPlugin', () => {
|
||||
gtag('no_position_found_for_current_job')
|
||||
|
||||
@@ -8,8 +8,9 @@ export let mainWindow: BrowserWindow | null = null
|
||||
export function createMainWindow(): BrowserWindow {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1024,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
minWidth: 1280,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
...(process.platform === 'linux'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="job-combo-filter">
|
||||
<div class="filter-item">
|
||||
<div>薪资待遇</div>
|
||||
<div font-size-12px>薪资待遇</div>
|
||||
<el-select
|
||||
v-model="modelValue.salaryList"
|
||||
multiple
|
||||
@@ -18,7 +18,7 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<div>工作经验</div>
|
||||
<div font-size-12px>工作经验</div>
|
||||
<el-select
|
||||
v-model="modelValue.experienceList"
|
||||
multiple
|
||||
@@ -35,7 +35,7 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<div>学历要求</div>
|
||||
<div font-size-12px>学历要求</div>
|
||||
<el-select
|
||||
v-model="modelValue.degreeList"
|
||||
multiple
|
||||
@@ -52,7 +52,7 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<div>公司行业</div>
|
||||
<div font-size-12px>公司行业</div>
|
||||
<el-select
|
||||
v-model="modelValue.industryList"
|
||||
multiple
|
||||
@@ -75,7 +75,7 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<div>公司规模</div>
|
||||
<div font-size-12px>公司规模</div>
|
||||
<el-select
|
||||
v-model="modelValue.scaleList"
|
||||
multiple
|
||||
|
||||
@@ -1,499 +0,0 @@
|
||||
<template>
|
||||
<div class="form-wrap geek-auto-start-run-with-boss">
|
||||
<el-form ref="formRef" :model="formContent" label-position="top" :rules="formRules">
|
||||
<el-form-item label="BOSS直聘 Cookie">
|
||||
<el-button size="small" type="primary" @click="handleClickLaunchLogin"
|
||||
>编辑Cookie</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item
|
||||
label="钉钉机器人 AccessToken(用于记录开聊,请勿使用公司内部群)"
|
||||
prop="dingtalkRobotAccessToken"
|
||||
>
|
||||
<el-input v-model="formContent.dingtalkRobotAccessToken" />
|
||||
</el-form-item> -->
|
||||
<div>
|
||||
<el-form-item mb0>
|
||||
<div>
|
||||
是否查看职位详情的条件
|
||||
<span font-size-12px>(以下条件为空表示不筛选)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="expectCompanies" mb10px>
|
||||
<div
|
||||
v-full
|
||||
font-size-12px
|
||||
flex
|
||||
:style="{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
期望公司(以逗号分隔,不区分大小写)<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom-start"
|
||||
@show="gtagRenderer('tooltip_show_about_expect_company_figure')"
|
||||
>
|
||||
<template #content>
|
||||
<img block h-270px src="./resources/intro-of-job-entry.png" />
|
||||
</template>
|
||||
<el-button type="text" font-size-12px
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span>期望公司信息位置图示</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</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"
|
||||
@blur="normalizeExpectCompanies"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div mb42px>
|
||||
<el-form-item mb0>
|
||||
查看职位详情后,是发起投递还是标记不合适的条件
|
||||
<span font-size-12px>(以下条件为空表示不筛选)</span>
|
||||
</el-form-item>
|
||||
<div
|
||||
flex
|
||||
:style="{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom"
|
||||
@show="gtagRenderer('tooltip_show_about_expect_job_info_figure')"
|
||||
>
|
||||
<template #content>
|
||||
<img block h-270px src="./resources/intro-of-job-info.png" />
|
||||
</template>
|
||||
<el-button type="text" font-size-12px
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span>如下各信息位置图示</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom-start"
|
||||
@show="gtagRenderer('tooltip_show_about_mark_not_suit_intro')"
|
||||
>
|
||||
<template #content>
|
||||
<ol m0 line-height-1.5em w-400px pl2em>
|
||||
<li>
|
||||
如果查找到的职位,职位名称、职位类型、职位描述与如下正则不匹配,则这个职位将被标记为不合适
|
||||
</li>
|
||||
<li>
|
||||
如果查找到的职位活跃时间为“本月活跃”或更往前的时间,则这个职位将被标记为不合适
|
||||
</li>
|
||||
<li>
|
||||
如有错误标记,请在左侧“<a
|
||||
href="javascript:void(0)"
|
||||
style="color: var(--el-color-primary)"
|
||||
@click.prevent="
|
||||
() => {
|
||||
gtagRenderer('click_view_mansr_from_boss_b_tooltip')
|
||||
$router.push('/main-layout/MarkAsNotSuitRecord')
|
||||
}
|
||||
"
|
||||
>标记不合适</a
|
||||
>”记录中找到相关记录,手动对这些职位发起会话
|
||||
</li>
|
||||
</ol>
|
||||
</template>
|
||||
<el-button type="text" font-size-12px
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span>标记不合适机制</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</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>
|
||||
<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
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1em 1fr 1em 1fr',
|
||||
gap: '5px',
|
||||
width: '100%',
|
||||
alignItems: 'end'
|
||||
}"
|
||||
>
|
||||
<el-form-item mb0 prop="expectJobNameRegExpStr">
|
||||
<div font-size-12px>职位名称正则(不区分大小写)</div>
|
||||
<el-input
|
||||
v-model="formContent.expectJobNameRegExpStr"
|
||||
@blur="
|
||||
formContent.expectJobNameRegExpStr =
|
||||
formContent.expectJobNameRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div mb10px font-size-12px flex flex-justify-center>且</div>
|
||||
<el-form-item mb0 prop="expectJobTypeRegExpStr">
|
||||
<div font-size-12px>职位类型正则(不区分大小写)</div>
|
||||
<el-input
|
||||
v-model="formContent.expectJobTypeRegExpStr"
|
||||
@blur="
|
||||
formContent.expectJobTypeRegExpStr =
|
||||
formContent.expectJobTypeRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div mb10px font-size-12px flex flex-justify-center>且</div>
|
||||
<el-form-item mb0 prop="expectJobDescRegExpStr">
|
||||
<div font-size-12px>职位描述正则(不区分大小写)</div>
|
||||
<el-input
|
||||
v-model="formContent.expectJobDescRegExpStr"
|
||||
@blur="
|
||||
formContent.expectJobDescRegExpStr =
|
||||
formContent.expectJobDescRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item
|
||||
label="职位备选筛选条件(当前求职期望无合适职位时,自动更改Boss筛选条件,查找新工作)"
|
||||
prop="filter"
|
||||
mb0
|
||||
>
|
||||
<AnyCombineBossRecommendFilter v-model="formContent.anyCombineRecommendJobFilter" />
|
||||
<div>
|
||||
当前组合条件数:{{ currentAnyCombineRecommendJobFilterCombinationCount.toLocaleString() }}
|
||||
<span
|
||||
v-if="currentAnyCombineRecommendJobFilterCombinationCount >= 20"
|
||||
class="color-orange"
|
||||
>不建议选择太多组合条件</span
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="last-form-item mb0">
|
||||
<el-button @click="handleSave">仅保存配置</el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 保存配置,并开始求职! </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElForm, ElMessage } from 'element-plus'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AnyCombineBossRecommendFilter from '@renderer/features/AnyCombineBossRecommendFilter/index.vue'
|
||||
import { calculateTotalCombinations } from '@geekgeekrun/geek-auto-start-chat-with-boss/combineCalculator.mjs'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import defaultTargetCompanyListConf from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const formContent = ref({
|
||||
dingtalkRobotAccessToken: '',
|
||||
expectCompanies: '',
|
||||
anyCombineRecommendJobFilter: {},
|
||||
expectJobNameRegExpStr: '',
|
||||
expectJobTypeRegExpStr: '',
|
||||
expectJobDescRegExpStr: ''
|
||||
})
|
||||
|
||||
const currentAnyCombineRecommendJobFilterCombinationCount = computed(() => {
|
||||
return calculateTotalCombinations(formContent.value.anyCombineRecommendJobFilter)
|
||||
})
|
||||
|
||||
electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||
console.log(res)
|
||||
formContent.value.dingtalkRobotAccessToken = res.config['dingtalk.json']['groupRobotAccessToken']
|
||||
formContent.value.expectCompanies = res.config['target-company-list.json'].join(',')
|
||||
formContent.value.anyCombineRecommendJobFilter = res.config['boss.json']
|
||||
?.anyCombineRecommendJobFilter ?? {
|
||||
salaryList: [],
|
||||
experienceList: [],
|
||||
degreeList: [],
|
||||
scaleList: [],
|
||||
industryList: []
|
||||
}
|
||||
//
|
||||
if (
|
||||
res.config['boss.json']?.expectJobRegExpStr &&
|
||||
typeof res.config['boss.json']?.expectJobNameRegExpStr === 'undefined' &&
|
||||
typeof res.config['boss.json']?.expectJobTypeRegExpStr === 'undefined' &&
|
||||
typeof res.config['boss.json']?.expectJobDescRegExpStr === 'undefined'
|
||||
) {
|
||||
res.config['boss.json'].expectJobNameRegExpStr = res.config['boss.json'].expectJobRegExpStr
|
||||
res.config['boss.json'].expectJobTypeRegExpStr = res.config['boss.json'].expectJobRegExpStr
|
||||
res.config['boss.json'].expectJobDescRegExpStr = res.config['boss.json'].expectJobRegExpStr
|
||||
}
|
||||
formContent.value.expectJobNameRegExpStr = res.config['boss.json'].expectJobNameRegExpStr?.trim()
|
||||
formContent.value.expectJobTypeRegExpStr = res.config['boss.json'].expectJobTypeRegExpStr?.trim()
|
||||
formContent.value.expectJobDescRegExpStr = res.config['boss.json'].expectJobDescRegExpStr?.trim()
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
expectJobNameRegExpStr: {
|
||||
validator(_, value, cb) {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_expect_job_name')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_name')
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_name')
|
||||
}
|
||||
}
|
||||
},
|
||||
expectJobTypeRegExpStr: {
|
||||
validator(_, value, cb) {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_expect_job_type')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_type')
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_type')
|
||||
}
|
||||
}
|
||||
},
|
||||
expectJobDescRegExpStr: {
|
||||
validator(_, value, cb) {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_expect_job_desc')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_desc')
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_desc')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||
const handleSubmit = async () => {
|
||||
gtagRenderer('save_config_and_launch_clicked', {
|
||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken
|
||||
})
|
||||
formContent.value.expectJobRegExpStr = undefined
|
||||
try {
|
||||
await formRef.value!.validate()
|
||||
} catch (err) {
|
||||
ElMessage.error({
|
||||
message: '表单校验失败,请检查有误的内容',
|
||||
grouping: true
|
||||
})
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||
query: { flow: 'geek-auto-start-chat-with-boss' }
|
||||
})
|
||||
gtagRenderer('config_saved_and_launch_auto_start_chat', {
|
||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken
|
||||
})
|
||||
}
|
||||
const handleSave = async () => {
|
||||
gtagRenderer('save_config_clicked', {
|
||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken
|
||||
})
|
||||
normalizeExpectCompanies()
|
||||
try {
|
||||
await formRef.value!.validate()
|
||||
} catch (err) {
|
||||
ElMessage.error({
|
||||
message: '表单校验失败,请检查有误的内容',
|
||||
grouping: true
|
||||
})
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
||||
ElMessage.success('配置保存成功')
|
||||
gtagRenderer('config_saved')
|
||||
}
|
||||
|
||||
const normalizeExpectCompanies = () => {
|
||||
formContent.value.expectCompanies = formContent.value.expectCompanies
|
||||
.split(/,|,/)
|
||||
.map((it) => it.trim())
|
||||
.filter(Boolean)
|
||||
.join(',')
|
||||
}
|
||||
|
||||
const handleClickLaunchLogin = () => {
|
||||
gtagRenderer('launch_login_clicked')
|
||||
router.replace('/cookieAssistant')
|
||||
}
|
||||
const expectCompanyTemplateList = [
|
||||
{
|
||||
name: '默认值',
|
||||
value: defaultTargetCompanyListConf.join(',')
|
||||
},
|
||||
{
|
||||
name: '不限公司(随便投)',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
name: '大厂及关联企业',
|
||||
value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,巨量引擎,滴滴,嘀嘀,小桔,网易,有道,腾讯,酷狗,酷我,阅文,搜狗,京东,沃东天骏,达达,达冠,百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,智图,长地万方,瑞图万方,道道通,小熊博望,理想,蔚来,顺丰,讯飞,同程,艺龙,马蜂窝,贝壳,自如,链家,我爱我家,相寓,多点,金山,小米,猎豹,新浪,微博,阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,高德,LAZADA,来赞达,飞猪,菜鸟,哈啰,钉钉,乌鸫,饿了么,美团,三快,猫眼,快手,映客,小红书,行吟,奇虎,360,三六零,鸿盈,奇富,奇元,亚信,启明星辰,奇安信,深信服,长亭,绿盟,天融信,商汤,SenseTime,大华,海康威视,hikvision,汽车之家,车好多,瓜子,易车,昆仑万维,昆仑天工,闲徕,趣加,FunPlus,完美,马上消费,轻松,水滴,白龙马,58,车欢欢,五八,红布林,致美,快狗,天鹅到家,转转,美餐,知乎,智者四海,易点云,搜狐,用友,畅捷通,猿辅导,小猿,猿力,好未来,学而思,希望学,新东方,东方甄选,东方优选,作业帮,高途,跟谁学,学科网,天学网,一起教育,一起作业,美术宝,火花思维,粉笔,老虎国际,一心向上,向上一意,联想,拉勾,乐视,欢聚,竞技世界,拼多多,寻梦,得物,Moka,希瑞亚斯,北森,OPPO,欧珀,vivo,维沃,小天才,步步高,读书郎,货拉拉,陌陌,探探,Shopee,首汽租车,GoFun,神州租车,天眼查,旷视,小冰,美图,智谱华章,MiniMax,石头科技,迅雷,TP,希音,SHEIN,稀宇,深言,百川智能,与爱为舞,牵手,Grab,爱回收,洋钱罐,瓴岳,得到,思维造物,地平线,咪咕,翼支付,电信,天翼,联通,蓝湖,墨刀`
|
||||
},
|
||||
{
|
||||
name: '阿里系',
|
||||
value: `阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,飞猪,乌鸫,饿了么,LAZADA,来赞达,菜鸟,哈啰,钉钉,高德,白龙马,新浪,微博`
|
||||
},
|
||||
{
|
||||
name: '字节(头条/抖音)系',
|
||||
value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,巨量引擎`
|
||||
},
|
||||
{
|
||||
name: '百度系',
|
||||
value: `百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,作业帮,智图,长地万方,瑞图万方,道道通,小熊博望`
|
||||
},
|
||||
{
|
||||
name: '腾讯系',
|
||||
value: `腾讯,酷狗,酷我,阅文,搜狗,京东,沃东天骏,达达,达冠,美团,三快,猫眼,快手,拼多多,寻梦,Shopee,滴滴,嘀嘀,小桔`
|
||||
},
|
||||
{
|
||||
name: '外包、劳务派遣企业',
|
||||
value: `青钱,软通动力,南天,睿服,中电金信,佰钧成,云链,博彦,汉克时代,柯莱特,拓保,亿达信息,纬创,微创,微澜,诚迈科技,法本,兆尹,诚迈,联合永道,新致软件,宇信科技,华为,德科,FESCO,科锐,科之锐`
|
||||
}
|
||||
]
|
||||
function handleExpectCompanyTemplateClicked(item) {
|
||||
gtagRenderer('expect_company_tpl_clicked', {
|
||||
name: item.name
|
||||
})
|
||||
formContent.value.expectCompanies = item.value
|
||||
}
|
||||
|
||||
const expectJobFilterTemplateList = [
|
||||
{
|
||||
name: '不限职位(随便投)',
|
||||
config: {
|
||||
expectJobNameRegExpStr: '',
|
||||
expectJobTypeRegExpStr: '',
|
||||
expectJobDescRegExpStr: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '研发 - 前端开发工程师',
|
||||
config: {
|
||||
expectJobNameRegExpStr: '前端|H5|FE',
|
||||
expectJobTypeRegExpStr: '前端开发|javascript',
|
||||
expectJobDescRegExpStr: '前端|vue|react|node|js|javascript|H5'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '研发 - Java',
|
||||
config: {
|
||||
expectJobNameRegExpStr: '\\bJava\\b',
|
||||
expectJobTypeRegExpStr: '\\bJava\\b',
|
||||
expectJobDescRegExpStr: '\\bJava\\b|JVM|消息队列|MQ|MySQL|Nginx|Redis|Dubbo'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '人力 - 员工关系',
|
||||
config: {
|
||||
expectJobNameRegExpStr: '员工关系|劳动关系|SSC|人力资源|人资',
|
||||
expectJobTypeRegExpStr: '员工关系|人力资源',
|
||||
expectJobDescRegExpStr: '社保|考勤|入职|离职'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '人力 - 招聘',
|
||||
config: {
|
||||
expectJobNameRegExpStr: '招聘|招聘HR|招聘专员|招聘顾问|招聘专家|Recruiter|人力资源|人资',
|
||||
expectJobTypeRegExpStr: '招聘|人力资源|猎头顾问',
|
||||
expectJobDescRegExpStr: '简历筛选|面试安排|offer|猎头'
|
||||
}
|
||||
}
|
||||
]
|
||||
function handleExpectJobFilterTemplateClicked(item) {
|
||||
gtagRenderer('expect_job_filter_tpl_clicked', {
|
||||
name: item.name
|
||||
})
|
||||
|
||||
Object.assign(formContent.value, {
|
||||
...item.config
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-wrap {
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
:deep(.el-form) {
|
||||
padding-top: 8px;
|
||||
}
|
||||
.last-form-item {
|
||||
:deep(.el-form-item__content) {
|
||||
margin-top: 0px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.form-wrap.geek-auto-start-run-with-boss {
|
||||
.el-form-item__error.el-form-item__error {
|
||||
font-size: 12px;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div>
|
||||
<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
|
||||
gtagRenderer('choose_city_entry_button_clicked')
|
||||
}
|
||||
"
|
||||
>选择城市</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="modelValue?.length"
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleClearSelectedCitiesInModelValue"
|
||||
>清空已选择的所有城市</el-button
|
||||
>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="isDialogVisible"
|
||||
width="1000px"
|
||||
title="请选择城市"
|
||||
:show-close="false"
|
||||
@open="handleDialogOpen"
|
||||
@closed="handleDialogClosed"
|
||||
>
|
||||
<el-tabs v-model="activeTabName">
|
||||
<el-tab-pane
|
||||
:style="{ height: '300px', overflow: 'auto' }"
|
||||
label="热门城市"
|
||||
name="热门城市"
|
||||
>
|
||||
<el-checkbox-group v-model="selectedCities">
|
||||
<div
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr 1fr 1fr'
|
||||
}"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="op in hotCityList.filter((it) => it.code !== 100010000)"
|
||||
:key="op.code"
|
||||
:label="op.name"
|
||||
>
|
||||
{{ op.name }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-for="it in cityGroupsByAlphabetMap.keys()"
|
||||
:key="it"
|
||||
:style="{ height: '300px', overflow: 'auto' }"
|
||||
:label="it"
|
||||
:value="it"
|
||||
>
|
||||
<div v-for="group in cityGroupsByAlphabetMap.get(it)" :key="group.firstChar">
|
||||
{{ group.firstChar }}
|
||||
<el-checkbox-group v-model="selectedCities">
|
||||
<div
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr 1fr 1fr'
|
||||
}"
|
||||
>
|
||||
<el-checkbox v-for="op in group.cityList" :key="op.code" :label="op.name">
|
||||
{{ op.name }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div
|
||||
:style="{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="selectedCities.length"
|
||||
type="danger"
|
||||
@click="handleClearSelectedCitiesInDialog"
|
||||
>清空已选择的所有城市</el-button
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<el-button @click="handleCancelClicked">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirmClicked">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType, ref } from 'vue'
|
||||
import cityGroupData from '../../../../../../common/constant/cityGroup.json'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<string[]>
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const { hotCityList, cityGroup } = cityGroupData.zpData
|
||||
|
||||
const activeTabName = ref('热门城市')
|
||||
const isDialogVisible = ref(false)
|
||||
const selectedCities = ref([])
|
||||
|
||||
const cityGroupsByAlphabetMap = ref(
|
||||
new Map(['ABCDE', 'FGHJ', 'KLMN', 'PQRST', 'WXYZ'].map((it) => [it, []]))
|
||||
)
|
||||
for (const group of cityGroup) {
|
||||
const { firstChar } = group
|
||||
const targetKey =
|
||||
[...cityGroupsByAlphabetMap.value.keys()].find((it) => it.includes(firstChar)) ?? null
|
||||
if (!targetKey) {
|
||||
if (!cityGroupsByAlphabetMap.value.get(targetKey)) {
|
||||
cityGroupsByAlphabetMap.value.set(targetKey, [])
|
||||
}
|
||||
}
|
||||
cityGroupsByAlphabetMap.value.get(targetKey)?.push(group)
|
||||
}
|
||||
|
||||
function handleDialogOpen() {
|
||||
activeTabName.value = '热门城市'
|
||||
selectedCities.value = [...(props.modelValue ?? [])]
|
||||
gtagRenderer('choose_city_dialog_open')
|
||||
}
|
||||
|
||||
function handleCancelClicked() {
|
||||
gtagRenderer('choose_city_cancel_button_clicked')
|
||||
isDialogVisible.value = false
|
||||
}
|
||||
function handleConfirmClicked() {
|
||||
gtagRenderer('choose_city_confirm_button_clicked', { value: selectedCities.value.join(',') })
|
||||
isDialogVisible.value = false
|
||||
emits('update:modelValue', [...(selectedCities.value ?? [])])
|
||||
}
|
||||
function handleDialogClosed() {
|
||||
selectedCities.value = []
|
||||
gtagRenderer('choose_city_dialog_closed')
|
||||
}
|
||||
|
||||
function handleClearSelectedCitiesInModelValue() {
|
||||
emits('update:modelValue', [])
|
||||
gtagRenderer('clear_selected_cities_in_mv_clicked')
|
||||
}
|
||||
function handleClearSelectedCitiesInDialog() {
|
||||
selectedCities.value = []
|
||||
gtagRenderer('clear_selected_cities_in_dialog_clicked')
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,18 @@
|
||||
<template v-else-if="row.markReason === MarkAsNotSuitReason.JOB_NOT_SUIT">
|
||||
<strong>{{ markReasonTopicMap[row.markReason] }}</strong>
|
||||
</template>
|
||||
<template v-if="row.markReason === MarkAsNotSuitReason.JOB_CITY_NOT_SUIT">
|
||||
<strong>{{ markReasonTopicMap[row.markReason] }}</strong>
|
||||
<pre class="m-0 of-auto">{{ formatMarkReason(row) }}</pre>
|
||||
</template>
|
||||
<template v-if="row.markReason === MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT">
|
||||
<strong>{{ markReasonTopicMap[row.markReason] }}</strong>
|
||||
<pre class="m-0 of-auto">{{ formatMarkReason(row) }}</pre>
|
||||
</template>
|
||||
<template v-if="row.markReason === MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT">
|
||||
<strong>{{ markReasonTopicMap[row.markReason] }}</strong>
|
||||
<pre class="m-0 of-auto">{{ formatMarkReason(row) }}</pre>
|
||||
</template>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="experienceName" label="工作经验" />
|
||||
@@ -139,7 +151,7 @@ async function getMarkAsNotSuitRecord() {
|
||||
try {
|
||||
gtagRenderer('mansr_request_sent', {
|
||||
page_no: pagination.value.pageNo,
|
||||
page_size: pagination.value.pageSize,
|
||||
page_size: pagination.value.pageSize
|
||||
})
|
||||
isTableLoading.value = true
|
||||
const { data: res } = (await electron.ipcRenderer.invoke('get-mark-as-not-suit-record', {
|
||||
@@ -154,13 +166,13 @@ async function getMarkAsNotSuitRecord() {
|
||||
}
|
||||
gtagRenderer('mansr_request_success', {
|
||||
page_no: pagination.value.pageNo,
|
||||
page_size: pagination.value.pageSize,
|
||||
page_size: pagination.value.pageSize
|
||||
})
|
||||
} catch (err) {
|
||||
gtagRenderer('mansr_request_error', {
|
||||
err,
|
||||
page_no: pagination.value.pageNo,
|
||||
page_size: pagination.value.pageSize,
|
||||
page_size: pagination.value.pageSize
|
||||
})
|
||||
console.log(err)
|
||||
tableData.value = []
|
||||
@@ -204,7 +216,10 @@ function handleViewJobSnapshotButtonClick(record: VMarkAsNotSuitLog) {
|
||||
const markReasonTopicMap = {
|
||||
[MarkAsNotSuitReason.BOSS_INACTIVE]: 'Boss不活跃',
|
||||
[MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON]: '手动标记不合适',
|
||||
[MarkAsNotSuitReason.JOB_NOT_SUIT]: '职位不合适'
|
||||
[MarkAsNotSuitReason.JOB_NOT_SUIT]: '职位不合适',
|
||||
[MarkAsNotSuitReason.JOB_CITY_NOT_SUIT]: '工作地不合适',
|
||||
[MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT]: '工作经验不合适',
|
||||
[MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT]: '薪资不合适'
|
||||
}
|
||||
|
||||
function formatMarkReason(row: VMarkAsNotSuitLog) {
|
||||
@@ -236,6 +251,34 @@ function formatMarkReason(row: VMarkAsNotSuitLog) {
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT:
|
||||
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: {
|
||||
const extInfo = (() => {
|
||||
try {
|
||||
return JSON.parse(row.extInfo)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
return [extInfo?.chosenReasonInUi?.text && `Boss选项内容:${extInfo.chosenReasonInUi.text}`]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: {
|
||||
const extInfo = (() => {
|
||||
try {
|
||||
return JSON.parse(row.extInfo)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
return [
|
||||
extInfo?.salaryDesc && `薪资:${extInfo.salaryDesc}`,
|
||||
extInfo?.chosenReasonInUi?.text && `Boss选项内容:${extInfo.chosenReasonInUi.text}`
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
default: {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -11,6 +11,30 @@
|
||||
>编辑Cookie</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<el-checkbox v-if="!expectJobTypeRegExpStr?.trim()" :model-value="false" disabled>
|
||||
发送提醒消息前,先按照“Boss炸弹-职位类型正则”校验正在与Boss沟通的岗位是否满足期望,校验通过后再提醒
|
||||
</el-checkbox>
|
||||
<template v-else>
|
||||
<el-checkbox v-model="formContent.autoReminder.onlyRemindBossWithExpectJobType">
|
||||
发送提醒消息前,先按照“Boss炸弹-职位类型正则”校验正在与Boss沟通的岗位是否满足期望,校验通过后再提醒
|
||||
</el-checkbox>
|
||||
<div ml1.5em color-gray>
|
||||
<div>当前职位类型正则:{{ expectJobTypeRegExpStr?.trim() }}</div>
|
||||
<template
|
||||
v-if="
|
||||
formContent.autoReminder.rechatContentSource ===
|
||||
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||
"
|
||||
>
|
||||
<div>当前简历中填写的期望职位:{{ resumeContent?.expectJob ?? '-' }}</div>
|
||||
<div color-orange>请确保上方二者信息匹配</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb0" label="跟进话术 - 当发现已读不回的Boss时,将要向Boss发出:">
|
||||
<el-radio-group v-model="formContent.autoReminder.rechatContentSource">
|
||||
<div>
|
||||
@@ -150,7 +174,7 @@
|
||||
@blur="handleThrottleIntervalMinutesBlur"
|
||||
/> 分钟内不多次跟进同一Boss
|
||||
</el-form-item>
|
||||
<el-form-item label="跟进时限(天)" prop="rechatLimitDay">
|
||||
<el-form-item label="跟进时限(天)" prop="rechatLimitDay" mb-0>
|
||||
<div>
|
||||
<div><el-checkbox v-model="enableRechatLimit" /> 启用</div>
|
||||
<el-input-number
|
||||
@@ -168,6 +192,30 @@
|
||||
<div v-else>这将会跟进列表中所有聊天(<span class="text-orange">不建议</span>)</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom-start"
|
||||
@show="gtagRenderer('tooltip_show_about_stop_trace_one_boss')"
|
||||
>
|
||||
<template #content>
|
||||
<ul m0 line-height-1.5em w-300px pl2em>
|
||||
<li>
|
||||
请向你不想继续提醒的Boss发送任意消息,发送后立即撤回的这条消息即可。
|
||||
<br />
|
||||
<br />
|
||||
对于PC端Boss直聘,鼠标移动到要撤回的消息,点按鼠标右键调出菜单,再鼠标左键点击菜单中的“撤回”。如图所示:
|
||||
<br />
|
||||
<img block w-full src="./resources/withdraw-message-guide.png" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<el-button type="text" font-size-12px
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span
|
||||
>我不想持续提醒某个Boss了,如何处理?</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item class="last-form-item">
|
||||
<el-button type="primary" @click="handleSubmit">开始提醒</el-button>
|
||||
</el-form-item>
|
||||
@@ -184,6 +232,8 @@ import {
|
||||
RECHAT_LLM_FALLBACK
|
||||
} from '../../../../common/enums/auto-start-chat'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import mittBus from '../../utils/mitt'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const formContent = ref({
|
||||
@@ -192,7 +242,8 @@ const formContent = ref({
|
||||
rechatLimitDay: 21,
|
||||
rechatContentSource: 1,
|
||||
recentMessageQuantityForLlm: 8,
|
||||
rechatLlmFallback: RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION
|
||||
rechatLlmFallback: RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION,
|
||||
onlyRemindBossWithExpectJobType: true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -224,10 +275,32 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||
? 8
|
||||
: parseInt(conf.recentMessageQuantityForLlm)
|
||||
: 8
|
||||
conf.onlyRemindBossWithExpectJobType = conf.onlyRemindBossWithExpectJobType ?? true
|
||||
conf.rechatLlmFallback = conf.rechatLlmFallback ?? RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION
|
||||
formContent.value.autoReminder = conf
|
||||
})
|
||||
|
||||
const expectJobTypeRegExpStr = ref('')
|
||||
async function fetchExpectJobTypeRegExpStr() {
|
||||
await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||
expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr
|
||||
})
|
||||
}
|
||||
fetchExpectJobTypeRegExpStr()
|
||||
mittBus.on('auto-start-chat-with-boss-config-saved', fetchExpectJobTypeRegExpStr)
|
||||
onUnmounted(() => {
|
||||
mittBus.off('auto-start-chat-with-boss-config-saved', fetchExpectJobTypeRegExpStr)
|
||||
})
|
||||
|
||||
const resumeContent = ref(null)
|
||||
async function fetchResumeContent() {
|
||||
await electron.ipcRenderer.invoke('fetch-resume-content').then((res) => {
|
||||
resumeContent.value = res
|
||||
})
|
||||
}
|
||||
|
||||
fetchResumeContent()
|
||||
|
||||
const formRules = {
|
||||
throttleIntervalMinutes: {
|
||||
validator(_, value, cb) {
|
||||
@@ -274,6 +347,7 @@ async function checkIsCanRun() {
|
||||
gtagRenderer('invalid_rc_dialog_click_confirm')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('resume-edit')
|
||||
await fetchResumeContent()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
@@ -449,6 +523,7 @@ const handleClickEditResume = async () => {
|
||||
gtagRenderer('edit_resume_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('resume-edit')
|
||||
await fetchResumeContent()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
@@ -483,13 +558,13 @@ async function handleTestEffectClicked() {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-wrap {
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
:deep(.el-form) {
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.last-form-item {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 346 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -45,7 +45,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
children: [
|
||||
{
|
||||
path: 'GeekAutoStartChatWithBoss',
|
||||
component: () => import('@renderer/page/MainLayout/GeekAutoStartChatWithBoss.vue'),
|
||||
component: () => import('@renderer/page/MainLayout/GeekAutoStartChatWithBoss/index.vue'),
|
||||
meta: {
|
||||
title: 'BOSS炸弹'
|
||||
}
|
||||
|
||||
3
packages/ui/src/renderer/src/utils/mitt.ts
Normal file
3
packages/ui/src/renderer/src/utils/mitt.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import mitt from 'mitt'
|
||||
const mittBus = mitt()
|
||||
export default mittBus
|
||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -132,6 +132,9 @@ importers:
|
||||
minimist:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
mitt:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
node-machine-id:
|
||||
specifier: ^1.1.12
|
||||
version: 1.1.12
|
||||
@@ -206,8 +209,8 @@ importers:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(vite@5.1.1)
|
||||
element-plus:
|
||||
specifier: ^2.5.5
|
||||
version: 2.5.5(vue@3.4.15)
|
||||
specifier: ^2.8.4
|
||||
version: 2.8.4(vue@3.4.15)
|
||||
eslint:
|
||||
specifier: ^8.56.0
|
||||
version: 8.56.0
|
||||
@@ -3082,6 +3085,11 @@ packages:
|
||||
|
||||
/dayjs@1.11.10:
|
||||
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
||||
dev: false
|
||||
|
||||
/dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
dev: true
|
||||
|
||||
/de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
@@ -3357,8 +3365,8 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/element-plus@2.5.5(vue@3.4.15):
|
||||
resolution: {integrity: sha512-yGU/MruLOWI0ImQPFCyFM4cXtHtILJNAi0hhLImcxjRukjgQLYNkvcvbwqNgBUt808KSeKF9MyxENFyBQLTg+Q==}
|
||||
/element-plus@2.8.4(vue@3.4.15):
|
||||
resolution: {integrity: sha512-ZlVAdUOoJliv4kW3ntWnnSHMT+u/Os7mXJjk2xzOlqNeHaI2/ozlF+R58ZCEak8ZnDi6+5A2viWEYRsq64IuiA==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
@@ -3370,7 +3378,7 @@ packages:
|
||||
'@types/lodash-es': 4.17.12
|
||||
'@vueuse/core': 9.13.0(vue@3.4.15)
|
||||
async-validator: 4.2.5
|
||||
dayjs: 1.11.10
|
||||
dayjs: 1.11.13
|
||||
escape-html: 1.0.3
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
|
||||
Reference in New Issue
Block a user