Compare commits

...

72 Commits

Author SHA1 Message Date
geekgeekrun
d21972020e ui-v0.9.4 2025-10-14 01:52:07 +08:00
geekgeekrun
51ca5dd71d fix when choose "应届生" in "工作经验", every job is marked as not suit 2025-10-14 01:49:57 +08:00
geekgeekrun
e7d5ef382e ui-v0.9.3 2025-10-13 00:44:31 +08:00
geekgeekrun
59a36dc7bd Merge remote-tracking branch 'origin/master' into feature/ui 2025-10-13 00:43:05 +08:00
geekgeekrun
73b57e21e4 fix tip of daily chat startup chance used out. fix noOpCondition is not take effect. 2025-10-13 00:42:48 +08:00
geekgeekrun
6f0d98653c enhance ui text 2025-10-13 00:39:31 +08:00
geekgeekrun
3bf6d4c358 enhance electron.vite.config.ts 2025-10-04 01:24:18 +08:00
geekgeekrun
2c7ecafee2 fix issue will cause app crash when mark job not suit due to salary; fix salary compare logic 2025-10-04 01:23:01 +08:00
geekgeekrun
f88a5a2889 Merge branch 'feature/ui' 2025-08-17 13:51:09 +08:00
geekgeekrun
ac19ae3832 ui-v0.9.2 2025-08-17 11:16:42 +08:00
geekgeekrun
a25b047f99 fix ci error  ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with packages/ui/package.json 2025-08-17 11:16:14 +08:00
geekgeekrun
42fc66a0f4 fix cannot run due to lodash-es is not bundled; fix logic about set security question tip modal 2025-08-17 11:11:15 +08:00
geekgeekrun
203fbba9a0 ui-v0.9.1 2025-08-17 01:53:49 +08:00
geekgeekrun
b821bc500c fix error for cannot find chooseReasonDialogProxy 2025-08-17 01:50:51 +08:00
geekgeekrun
0032a356e5 fix: reduce argument getBossIdsByJobIds count to fix error: Error: SQLITE_ERROR: Expression tree is too large (maximum depth 1000) 2025-08-17 00:04:05 +08:00
geekgeekrun
b9c4976291 fix: cannot run due to QueryFailedError: SQLITE_ERROR: near ")": syntax error - when one of jobNotMatchStrategy/jobNotActiveStrategy/expectCityNotMatchStrategy set to MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL and chattedJobIds.length === 0, application will crash 2025-08-17 00:00:34 +08:00
geekgeekrun
558891ad9f Merge branch 'master' into feature/ui 2025-08-16 16:38:34 +08:00
geekgeekrun-maintainer
d2da9f5a7d Update README.md 2025-07-31 01:18:29 +08:00
geekgeekrun-maintainer
71068be21f Update README.md 2025-07-25 02:27:45 +08:00
geekgeekrun-maintainer
2b0cf2a2fa Update README.md 2025-07-15 13:44:41 +08:00
geekgeekrun-maintainer
ceaf68e4eb Update README.md 2025-07-15 13:28:39 +08:00
geekgeekrun-maintainer
10cc248784 Update README.md 2025-07-15 13:23:04 +08:00
geekgeekrun
1d155e498a ui-v0.9.0 2025-07-13 21:12:01 +08:00
geekgeekrun
d3e086f321 Merge branch 'feature/ui' 2025-07-13 21:10:34 +08:00
geekgeekrun
5f97b3ccec Merge remote-tracking branch 'origin/master' into feature/ui 2025-07-13 21:10:07 +08:00
geekgeekrun
7dd0c675d5 enhance agreement 2025-07-13 21:09:40 +08:00
geekgeekrun-maintainer
c1978417da Update README.md 2025-07-13 20:16:12 +08:00
geekgeekrun
e0670a9f30 enhance gtag 2025-07-13 19:53:25 +08:00
geekgeekrun
b05031be8a adjust job-source-dragged gtag value 2025-07-13 18:48:05 +08:00
geekgeekrun
7b10e97fa8 collect jobSource when manual startup chat running; add jobSource in gtag both auto or manual 2025-07-13 18:40:59 +08:00
geekgeekrun
e867acb39d collect jobSource when auto startup chat running 2025-07-13 14:27:43 +08:00
geekgeekrun
b31224a81c add job source column migration for db 2025-07-13 14:19:23 +08:00
geekgeekrun
ddb0cb97d7 add gtag 2025-07-13 11:32:54 +08:00
geekgeekrun
f338dcffc8 enhance ui text for SearchSourceTipText 2025-07-13 01:32:36 +08:00
geekgeekrun
84a79c9bae enhance job-detail-filter error style 2025-07-13 00:55:43 +08:00
geekgeekrun
44b0e44e2e enhance ux of job source 2025-07-13 00:34:57 +08:00
geekgeekrun
08028f37dd add fallback logic when find no JobSource enabled 2025-07-12 20:04:32 +08:00
geekgeekrun
a0b09ebae4 Merge remote-tracking branch 'origin/master' into feature/ui 2025-07-12 19:32:50 +08:00
geekgeekrun
f078500c14 add core logic for job source 2025-07-12 19:31:14 +08:00
geekgeekrun
b89d6e264d fix wrong call way for mainFlowWillLaunch hook 2025-07-12 18:50:40 +08:00
geekgeekrun
78ddc949f4 enhance ui text 2025-07-12 13:01:48 +08:00
geekgeekrun
a208ffe7d4 fix TypeError: notSuitConditionHandleMap[markOnBossCondition] is not a function 2025-07-08 10:07:18 +08:00
geekgeekrun
bc70031e2b add desc of position filter 2025-07-06 04:33:34 +08:00
geekgeekrun
cc821f46f5 add logic to skip chatted boss in 30 days for auto-startup-chat 2025-07-06 02:51:12 +08:00
geekgeekrun
f668b3e5f5 add the logic to mark unsuitable job on local 2025-07-06 02:00:53 +08:00
geekgeekrun
d1441b5761 WIP: add the feature to apply job from recommend list and search list - TODO: handle mark as not suit 2025-07-05 20:09:17 +08:00
geekgeekrun-maintainer
e88b4da53a Update README.md 2025-07-05 12:25:27 +08:00
geekgeekrun-maintainer
d218778690 Update README.md 2025-07-05 12:14:12 +08:00
geekgeekrun-maintainer
f903ef9175 Update README.md 2025-07-05 03:08:29 +08:00
geekgeekrun-maintainer
d114685116 Update README.md 2025-07-05 02:52:25 +08:00
geekgeekrun
bb30329d6c move backup filter condition to top 2025-07-04 21:44:50 +08:00
geekgeekrun
3683ead1d4 allow skip empty filter condition when set backup filter condition to search job 2025-07-04 21:25:47 +08:00
geekgeekrun
cac3e480e5 enhance intro text about job detail filter; adjust expectJobFilterTemplateList 2025-06-29 15:27:48 +08:00
geekgeekrun
52163342c3 add jobDetailRegExpMatchLogic option related flow in core script 2025-06-29 14:48:40 +08:00
geekgeekrun
8efce15fd5 enhance ui after add jobDetailRegExpMatchLogic option; change layout of job detail RegExp option 2025-06-29 05:30:39 +08:00
geekgeekrun
d5dcdb4bd1 add jobDetailRegExpMatchLogic option in UI 2025-06-29 04:32:28 +08:00
geekgeekrun
98b99c5d14 Merge branch 'master' into feature/ui 2025-06-29 02:19:54 +08:00
geekgeekrun-maintainer
d4fa72a28e Update README.md 2025-06-28 12:46:55 +08:00
geekgeekrun
b024c6b517 ui-v0.8.0 2025-06-08 10:23:32 +08:00
geekgeekrun
9b64f05cb6 Merge branch 'feature/ui' 2025-06-08 10:22:48 +08:00
geekgeekrun
eefb25a3da enhance ui appearance 2025-06-08 10:20:07 +08:00
geekgeekrun
2dad39c8e4 disable 在校生 option of work exp 2025-06-03 00:55:56 +08:00
geekgeekrun
699bdd2641 add filter logic of job salary in script 2025-06-03 00:55:44 +08:00
geekgeekrun
48f1232f7d adjust priority of mark as not suit strategy when a job match more than one condition 2025-06-02 21:53:12 +08:00
geekgeekrun
2409699ccf add filter logic of job work exp in script 2025-06-02 16:26:31 +08:00
geekgeekrun
64c007066e fix ui display 2025-06-02 11:05:43 +08:00
geekgeekrun
bd503eb268 add work experience filter ui 2025-06-01 23:20:18 +08:00
geekgeekrun
8dc8ca825d add salary filter ui 2025-06-01 21:41:09 +08:00
geekgeekrun-maintainer
8e8fb6d331 Update README.md 2025-06-01 14:59:54 +08:00
geekgeekrun
2f3a557fb5 update element-plus 2025-05-31 15:52:15 +08:00
geekgeekrun
780cd73e46 Merge remote-tracking branch 'origin/master' 2025-05-30 23:40:07 +08:00
geekgeekrun-maintainer
6c05128e89 Update README.md 2025-05-29 23:42:23 +08:00
32 changed files with 2598 additions and 546 deletions

View File

@@ -12,33 +12,34 @@
## 程序有哪些功能?运行逻辑是什么?怎样使用?
### Boss炸弹
扩列神器按照你所设置的求职偏好自动开聊推荐职位列表中的匹配的Boss。
扩列神器按照你所设置的求职偏好自动开聊推荐职位列表中的匹配的Boss。可以帮你开聊推荐列表中的职位、根据设置的求职期望推荐的职位以及根据你设置的关键词通过搜索找到的职位;可以通过拖拽的方式自定义搜索顺序哦~
<img width="1024" alt="image" src="https://github.com/user-attachments/assets/ebf5c51c-f52c-4426-a60a-97916f663c6a" />
<img width="1280" height="2474" alt="Snipaste_2025-07-15_13-05-35" src="https://github.com/user-attachments/assets/30308ec2-1e3b-4a8f-b3fe-7d205394578d" />
#### 匹配步骤
1. 按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
2. 检查Boss活跃度
- 如果Boss活跃度为本月活跃或更往前的时间则会把职位标记为不合适一段时间内你将不会在Boss上看到这个职位且将会推荐新职位置换这个职位
3. 对职位名称、职位类型、职位描述进行匹配
- 如果匹配则自动点击开聊按钮
- 不匹配则标记这个职位为不合适一段时间内你将不会在Boss上看到这个职位且将会推荐新职位置换这个职位
1. 选择职位来源、职位筛选条件
<img width="1680" height="1050" alt="image" src="https://github.com/user-attachments/assets/cbdea071-ec28-49b0-af12-1018aa9579a9" />
2. 按照公司名称在列表中查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
3. 对工作地、薪资、工作经验、职位名称、职位类型、职位描述、Boss活跃度进行匹配
- 如果可以匹配,则自动点击开聊按钮
- 不匹配则根据你设置的标记不合适的策略将职位标记为不合适
- 首先查看是否命中策略为“在Boss直聘上标记不合适”的配置如果命中则在Boss直聘上点击相关UI标记不合适一段时间内你将不会在Boss上看到这个职位且将会推荐新职位置换这个职位对于“推荐列表中的职位”、“通过搜索找到的职位”此项会回落到“在本地数据库中标记不合适且7天内再遇到这个职位时直接跳过”
- 然后查看是否命中策略为“在本地数据库中标记不合适且7天内再遇到这个职位时直接跳过”的配置如果命中则仅在本地数据库中记录且本次运行将无视这个职位
- 最后查看是否命中策略为了“仅在本次运行中记录不合适,且本次运行再遇到这个职位时直接跳过”的配置,如果命中则本次运行将无视这个职位,但下次运行可能还会查看这个职位
#### 异常情况
- 当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
- 当前页面筛选条件下,如果没有更多可投递职位,则自动切换筛选条件,以获取更多新职位;如果所有筛选条件搜索完没有更多可投递职位,则切换其它职位来源继续查找更多新职位;如果所有职位来源筛选完没有更多可投递职位,则等候一段时间后,会回到第一个职位来源的第一个筛选条件,从头开始查找职位
- 如当天开聊次数用完本程序会暂停运行60分钟之后尝试继续重新运行如重新运行时间已在第二天则将会继续开聊
#### 运行效果
> Boss直聘界面已改版如下是旧版Boss直聘界面
https://github.com/user-attachments/assets/672f1637-dd54-416c-922e-1ecf09b0874a
### 已读不回提醒器
Boss不明原因已读不回简历就是投不出去
@@ -118,6 +119,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) 来调试本程序,查看反馈本程序的日志(如下图)。
![image](https://github.com/user-attachments/assets/4ee9955c-e1c0-4399-9d00-ce89f6657bba)
在此先谢过了~
## 为什么要写这个程序?
### 个人而言
以我为例,我真的求职求累了
@@ -143,8 +157,8 @@ Boss不明原因已读不回简历就是投不出去
一个岗位可以收到一堆简历投递,大部分简历最终的归宿都是人才库或者垃圾场
当然,如果运气好:
- 好不容易过了 HR 面,到了 Offer 阶段HR / 用人部门 几乎都在极限压缩用人成本,经常能被恶心到,但你又没有其它更好 Offer ,不敢贸然放弃这个 Offer
- 终于你自我催眠(类似:“领导看我薪资低,所以一定会争取给我涨薪”、“领导看我薪资低,所以假设我加倍努力工作绩效一定会向我倾斜”、“领导画的饼很好,吃了一定能有好结果”),接了 Offer 入了职然而经过一段时间的接触最终结果完全不符合期望诸如“团队很难融入”、“协作方经常踢皮球”、“你需要做很多脏活累活而且要帮前人擦屁股”、“领导希望用最小成本把你招来这样裁员时可以最小成本把你打发走”、“团队需要新人来背C/M-/3.25/裁员指标”、“领导分配的工作和领导入职时画的诱人的饼完全不一致”)
- 好不容易过了 HR 面,到了 Offer 阶段HR / 用人部门 几乎都在极限压缩用人成本,经常能被恶心到,但你又没有其它更好 Offer ,不敢贸然放弃这个不满意的 Offer
- 终于你自我催眠(类似:“领导看我薪资低,所以一定会争取给我涨薪”、“领导看我薪资低,所以假设我加倍努力工作绩效一定会向我倾斜”、“领导画的饼很好,吃了一定能有好结果”),接了不满意的 Offer 入了职然而经过一段时间的接触最终结果完全不符合期望诸如“团队很难融入”、“协作方经常踢皮球”、“你需要做很多脏活累活而且要帮前人擦屁股”、“领导希望用最小成本把你招来这样裁员时可以最小成本把你打发走”、“团队需要新人来背C/M-/3.25/裁员指标”、“领导分配的工作和领导入职时画的诱人的饼完全不一致”)
为了避免这种情况的发生,找工作时还是需要尽可能多的面试,多个选择。如果真的不慎遭遇了这些情况,让你认为在当前岗位继续做下去的收益不如离职换工作的收益,那就做好走的准备吧
@@ -157,7 +171,27 @@ Boss不明原因已读不回简历就是投不出去
人工筛选这些职位会有很大的心智负担,真的会吐……
因此我在Boss炸弹代码中加入了清理机制将通过标记不合适的方式尝试让这些不活跃职位、不合适职位消失保证只开聊符合你口味的职位
因此我在Boss炸弹代码中加入了清理机制将通过标记不合适的方式尝试让这些不活跃职位、不合适职位消失保证只开聊符合你口味的职位
## 使用必读及免责声明
如下是使用必读及免责声明,请您务必逐条阅读;本程序首次启动时,您还会再看到一次;若您不接受如下提到的任何内容,请不要使用本程序。
- 本程序从某种程度上说属于辅助工具,与《<a href="https://about.zhipin.com/agreement/?id=registerprotocol_30" rel="noreferer noopener" target="blank">Boss直聘用户协议</a>》2023年3月版相关条款相违背您在注册Boss直聘时已签署过这一条款根据该条款`七、用户的平台使用义务`、`八、违约责任` 章节,如果一些非正常用户行为被风控监测到,您需要承受包括不仅限于**账号被强制退出登录**、**账号被限制使用**、**账号被封禁**等对您不利的风险;因此使用本程序即意味着**您愿意接受以上风险**,且如果相关风险发生,您需要自行承担相关后果,**本程序概不负责** 。
- 本程序需要存储您的登录凭据即Cookie来模拟您在Boss直聘上开聊Boss的行为本程序仅会把您的Cookie存储在本地并在您访问Boss直聘时将其传输到Boss直聘**不会泄露给第三方**也不会进行除自动开聊Boss以外的行为**请勿向他人泄漏您的Cookie**。
- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的Boss直聘账号**进行求职。
- 本程序原理是模拟用户在Boss直聘网页上寻找关键元素并进行点击操作Boss直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
- 您所在公司可能会对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;上级/HRBP团队可能会从 IT 团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级/HRBP团队了解到您正在求职建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及上级/HRBP报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。
- 本程序**没有内置任何付费功能****下载**、**使用**是**免费**的,任何人可以**免费获得**、**免费使用**。**作者没有利用本程序赚到过任何收入**。如果您是从GitHub以外的地方付费后“购买”的本程序或您被提示“必须付费后才能使用本程序”那**您大概率被骗了**,或者**您下载到了本程序修改版**。**本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方**。
- 本程序**不对您的求职过程与结果负责**为您开聊的职位均在Boss直聘上发布职位信息真实性由Boss直聘负责请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。
- 请在Boss直聘上自行**屏蔽您不期望投递的公司**如果您不希望您当前公司其它具有招聘账号的员工看到您在Boss直聘上活跃请**在Boss直聘上屏蔽当前公司及与之关联的公司**。
- 本程序经历过了多次测试理论上来说大部分情况下可以正常运行但可能也会出现测试用例覆盖不到位导致程序不按预期运行的情况如果您有顾虑建议通过VMware Workstation/Fusion、Oracle VirtualBox、Microsoft Hyper-V等虚拟化技术运行本程序。如果您在使用过程中遇上程序未按照预期执行的情况请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
## 想联系作者?想反馈使用体验上的问题?想找求职搭子?
<img width="360" alt="QQ Group QRCode" src="https://github.com/user-attachments/assets/67560948-467b-44f5-a3e3-c8713255d8b7" />
欢迎进群聊天(刚建的群,人有点少,大胆聊就好)
--------
@@ -167,10 +201,12 @@ Boss不明原因已读不回简历就是投不出去
祝你求职成功,事业顺利,事事顺心
## Star 数据 感谢支持
## 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>
感谢支持

View File

@@ -90,7 +90,7 @@ export function* combineFiltersWithConstraintsGenerator(selectedFilters) {
//#region get count of combinations
// 计算符合限制条件的组合数量
export function calculateTotalCombinations(selectedFilters) {
export function calculateTotalCombinations(selectedFilters, includeEmptyCondition) {
const {
salaryList = [],
experienceList = [],
@@ -106,8 +106,21 @@ export function calculateTotalCombinations(selectedFilters) {
const scaleComb = combineWithZero(scaleList, 0, scaleList.length) // Scale: 0 个或更多
const industryComb = combineWithZero(industryList, 0, 3) // Industry: 0-3 个
return [salaryComb, experienceComb, degreeComb, scaleComb, industryComb].reduce((accu, cur) => {
let result = [salaryComb, experienceComb, degreeComb, scaleComb, industryComb].reduce((accu, cur) => {
return accu * cur.length
}, 1)
if (!includeEmptyCondition) {
result -= 1
}
return result
}
//#endregion
export function checkAnyCombineBossRecommendFilterHasCondition(value) {
if (!Object.keys(value ?? {}).length) {
return false
}
return Object.keys(value).some((k) => {
return !!value[k]?.length
})
}

View File

@@ -1,3 +1,7 @@
export const activeDescList = [
'', "半年前活跃","近半年活跃","5月内活跃","4月内活跃","3月内活跃","2月内活跃","本月活跃","2周内活跃","本周活跃","3日内活跃","昨日活跃","今日活跃","刚刚活跃"
]
]
export const RECOMMEND_JOB_ENTRY_SELECTOR = `.c-expect-select a[ka="jobs_recommend_tab_click"]`
export const USER_SET_EXPECT_JOB_ENTRIES_SELECTOR = `.c-expect-select .expect-list .expect-item`
export const SEARCH_BOX_SELECTOR = `.c-search-input .search-input-box`

View File

@@ -6,6 +6,7 @@
"scaleList": [],
"industryList": []
},
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
"expectJobRegExpStr": "",
"jobNotMatchStrategy": 1,
"jobNotActiveStrategy": 1,
@@ -13,6 +14,7 @@
"expectCityList": [],
"expectCityNotMatchStrategy": 3,
"strategyScopeOptionWhenMarkJobCityNotMatch": 2,
"jobDetailRegExpMatchLogic": 1,
"autoReminder": {
"throttleIntervalMinutes": 10,
"rechatLimitDay": 21,
@@ -21,5 +23,11 @@
"recentMessageQuantityForLlm": 8,
"rechatLlmFallback": 1,
"onlyRemindBossWithExpectJobType": true
}
},
"jobSourceList": [
{
"type": "expect",
"enabled": true
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,8 @@ const main = async () => {
newChatStartup: new AsyncSeriesHook(['positionInfoDetail', 'chatRunningContext']),
noPositionFoundForCurrentJob: new SyncHook(),
noPositionFoundAfterTraverseAllJob: new SyncHook(),
errorEncounter: new SyncHook(['errorInfo'])
errorEncounter: new SyncHook(['errorInfo']),
encounterEmptyRecommendJobList: new AsyncSeriesHook(['args'])
}
initPlugins(hooks)
await hooks.daemonInitialized.callAsync()

View File

@@ -1,3 +1,4 @@
import { JobSource } from "../enums";
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm()
@@ -29,4 +30,9 @@ export class ChatStartupLog {
nullable: true
})
autoStartupChatRecordId?: number;
@Column({
nullable: true
})
jobSource?: JobSource;
}

View File

@@ -1,4 +1,4 @@
import { MarkAsNotSuitOp, MarkAsNotSuitReason } from "../enums";
import { JobSource, MarkAsNotSuitOp, MarkAsNotSuitReason } from "../enums";
import { requireTypeorm } from "../utils/module-loader";
import { ChatStartupFrom } from "./ChatStartupLog";
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm()
@@ -41,4 +41,9 @@ export class MarkAsNotSuitLog {
nullable: true
})
autoStartupChatRecordId?: number;
@Column({
nullable: true
})
jobSource?: JobSource;
}

View File

@@ -4,6 +4,8 @@ export enum MarkAsNotSuitReason {
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 {
@@ -16,3 +18,19 @@ export enum StrategyScopeOptionWhenMarkJobNotMatch {
ALL_JOB = 1,
ONLY_COMPANY_MATCHED_JOB = 2
}
export enum SalaryCalculateWay {
MONTH_SALARY = 1,
ANNUAL_PACKAGE = 2,
}
export enum JobDetailRegExpMatchLogic {
EVERY = 1,
SOME = 2,
}
export enum JobSource {
expect = 1,
recommend = 2,
search = 3,
}

View File

@@ -245,7 +245,7 @@ export async function saveChatStartupRecord(
ds: DataSource,
_jobInfo,
{ encryptUserId },
{ autoStartupChatRecordId = undefined, chatStartupFrom = undefined } = {}
{ autoStartupChatRecordId = undefined, chatStartupFrom = undefined, jobSource = undefined } = {}
) {
const { jobInfo } = _jobInfo;
@@ -256,7 +256,8 @@ export async function saveChatStartupRecord(
encryptCurrentUserId: encryptUserId,
encryptJobId: jobInfo.encryptId,
autoStartupChatRecordId,
chatStartupFrom
chatStartupFrom,
jobSource,
}
Object.assign(chatStartupLog, chatStartupLogPayload)
@@ -270,7 +271,7 @@ export async function saveMarkAsNotSuitRecord(
ds: DataSource,
_jobInfo,
{ encryptUserId },
{ autoStartupChatRecordId = undefined, markFrom = undefined, extInfo = undefined, markReason = undefined, markOp = undefined } = {}
{ autoStartupChatRecordId = undefined, markFrom = undefined, extInfo = undefined, markReason = undefined, markOp = undefined, jobSource = undefined } = {}
) {
const { jobInfo } = _jobInfo;
@@ -284,7 +285,8 @@ export async function saveMarkAsNotSuitRecord(
markFrom,
markReason,
extInfo: extInfo ? JSON.stringify(extInfo) : undefined,
markOp
markOp,
jobSource,
}
Object.assign(markAsNotSuitLog, markAsNotSuitLogPayload)
@@ -333,9 +335,33 @@ export async function getNotSuitMarkRecordsInLastSomeDays (ds: DataSource, days
const result = await repo.findBy({
date: Raw(alias => `DATE(${alias}) >= DATE('${
new Date(
Number(new Date()) - 7 * 24 * 60 * 60 * 1000
Number(new Date()) - days * 24 * 60 * 60 * 1000
).toISOString()
}')`)
})
return result
}
export async function getChatStartupRecordsInLastSomeDays (ds: DataSource, days = 0) {
const repo = ds.getRepository(ChatStartupLog)
const result = await repo.findBy({
date: Raw(alias => `DATE(${alias}) >= DATE('${
new Date(
Number(new Date()) - days * 24 * 60 * 60 * 1000
).toISOString()
}')`)
})
return result
}
export async function getBossIdsByJobIds (ds: DataSource, jobIds: string[] = []) {
const repo = ds.getRepository(JobInfo)
const result = await repo.find({
where: jobIds.map(
id => ({
encryptJobId: id
})
)
})
return result
}

View File

@@ -1,7 +1,6 @@
import "reflect-metadata";
import { type DataSource } from "typeorm";
import { requireTypeorm } from "./utils/module-loader";
import fs from 'node:fs'
import { BossInfo } from "./entity/BossInfo";
import { BossInfoChangeLog } from "./entity/BossInfoChangeLog";
@@ -23,13 +22,22 @@ import { ChatMessageRecord } from './entity/ChatMessageRecord'
import { LlmModelUsageRecord } from './entity/LlmModelUsageRecord'
import sqlite3 from 'sqlite3';
import { saveChatStartupRecord, saveJobInfoFromRecommendPage, saveMarkAsNotSuitRecord, getNotSuitMarkRecordsInLastSomeDays } from "./handlers";
import {
saveChatStartupRecord,
saveJobInfoFromRecommendPage,
saveMarkAsNotSuitRecord,
getNotSuitMarkRecordsInLastSomeDays,
getChatStartupRecordsInLastSomeDays,
getBossIdsByJobIds
} 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";
import { AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog1752380078526 } from "./migrations/1752380078526-AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog";
const lodashImportPromise = import('lodash-es')
export function initDb(dbFilePath) {
const { DataSource } = requireTypeorm()
@@ -65,6 +73,7 @@ export function initDb(dbFilePath) {
UpdateChatStartupLogTable1729182577167,
UpdateBossInfoTable1732032381304,
AddColumnForMarkAsNotSuitLog1746092370665,
AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog1752380078526
],
migrationsRun: true
});
@@ -111,6 +120,7 @@ export default class SqlitePlugin {
expectCityNotMatchStrategy,
blockJobNotSuit,
blockBossNotActive,
blockBossNotNewChat
}) => {
if (
jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL ||
@@ -118,8 +128,11 @@ export default class SqlitePlugin {
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) {
const last7DayMarkRecords = (await getNotSuitMarkRecordsInLastSomeDays(ds, 7)) ?? [];
if (
jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL ||
jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
) {
last7DayMarkRecords
.filter(it =>
[
@@ -134,7 +147,10 @@ export default class SqlitePlugin {
id => blockJobNotSuit.add(id)
)
}
if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
if (
jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL ||
jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
) {
last7DayMarkRecords
.filter(it => it.markReason === MarkAsNotSuitReason.BOSS_INACTIVE)
.map(
@@ -144,7 +160,10 @@ export default class SqlitePlugin {
id => blockJobNotSuit.add(id)
)
}
if (expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
if (
expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL ||
expectCityNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
) {
last7DayMarkRecords
.filter(it => it.markReason === MarkAsNotSuitReason.JOB_CITY_NOT_SUIT)
.map(
@@ -154,6 +173,21 @@ export default class SqlitePlugin {
id => blockJobNotSuit.add(id)
)
}
const last30DayChatStartupRecords = (await getChatStartupRecordsInLastSomeDays(ds, 30)) ?? [];
const chattedJobIds = last30DayChatStartupRecords.map(it => it.encryptJobId)
if (chattedJobIds.length === 0) {
return
}
const { chunk } = await lodashImportPromise
const chattedJobIdChunks = chunk(chattedJobIds, 200)
const chattedBossIds = [];
for (const chattedJobIdChunk of chattedJobIdChunks) {
const chattedBossIdChunk = ((await getBossIdsByJobIds(ds, chattedJobIdChunk)) ?? []).map(it => it.encryptBossId)
chattedBossIds.push(...chattedBossIdChunk)
}
for (const id of chattedBossIds) {
blockBossNotNewChat.add(id)
}
}
}
);
@@ -163,22 +197,24 @@ export default class SqlitePlugin {
await saveJobInfoFromRecommendPage(ds, _jobInfo);
});
hooks.newChatStartup.tapPromise("SqlitePlugin", async (_jobInfo, { chatStartupFrom = ChatStartupFrom.AutoFromRecommendList } = {}) => {
hooks.newChatStartup.tapPromise("SqlitePlugin", async (_jobInfo, { chatStartupFrom = ChatStartupFrom.AutoFromRecommendList, jobSource = undefined } = {}) => {
const ds = await this.initPromise;
return await saveChatStartupRecord(ds, _jobInfo, this.userInfo, {
autoStartupChatRecordId: this.runRecordId,
chatStartupFrom
chatStartupFrom,
jobSource
});
});
hooks.jobMarkedAsNotSuit.tapPromise("SqlitePlugin", async (_jobInfo, { markFrom = ChatStartupFrom.AutoFromRecommendList, markReason = undefined, extInfo = undefined, markOp = undefined } = {}) => {
hooks.jobMarkedAsNotSuit.tapPromise("SqlitePlugin", async (_jobInfo, { markFrom = ChatStartupFrom.AutoFromRecommendList, markReason = undefined, extInfo = undefined, markOp = undefined, jobSource = undefined } = {}) => {
const ds = await this.initPromise;
return await saveMarkAsNotSuitRecord(ds, _jobInfo, this.userInfo, {
autoStartupChatRecordId: this.runRecordId,
markFrom,
markReason,
extInfo,
markOp
markOp,
jobSource
});
});
}

View File

@@ -0,0 +1,64 @@
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";
import { JobSource } from "../enums";
const ViewEntities = [
VBossLibrary,
VChatStartupLog,
VCompanyLibrary,
VJobLibrary,
VMarkAsNotSuitLog,
]
export class AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog1752380078526 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", "jobSource")) {
await queryRunner.addColumn(
"mark_as_not_suit_log",
new TableColumn({
name: "jobSource",
type: "number",
isNullable: true,
})
);
await queryRunner.query(`UPDATE mark_as_not_suit_log SET jobSource=?`, [JobSource.expect]);
}
}
if (await queryRunner.hasTable("chat_startup_log")) {
if (!await queryRunner.hasColumn("chat_startup_log", "jobSource")) {
await queryRunner.addColumn(
"chat_startup_log",
new TableColumn({
name: "jobSource",
type: "number",
isNullable: true,
})
);
await queryRunner.query(`UPDATE chat_startup_log SET jobSource=?`, [JobSource.expect]);
}
}
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> {
}
}

View File

@@ -3,7 +3,7 @@ import { defineConfig, externalizeDepsPlugin, loadEnv } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import { presetUno, presetAttributify, presetIcons } from 'unocss'
import transformerDirective from "@unocss/transformer-directives";
import transformerDirective from '@unocss/transformer-directives'
import Replace from 'unplugin-replace/vite'
process.env = { ...process.env, ...loadEnv(process.env.NODE_ENV!, process.cwd()) }
@@ -13,11 +13,17 @@ export default defineConfig({
rollupOptions: {
external: []
},
minify: 'terser'
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
},
plugins: [
externalizeDepsPlugin({
exclude: ['@geekgeekrun/geek-auto-start-chat-with-boss', '@geekgeekrun/dingtalk-plugin', '@geekgeekrun/utils', 'find-chrome-bin', '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension']
exclude: [
'@geekgeekrun/geek-auto-start-chat-with-boss',
'@geekgeekrun/dingtalk-plugin',
'@geekgeekrun/utils',
'find-chrome-bin',
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension'
]
}),
Replace({
delimiters: ['', ''],
@@ -26,11 +32,11 @@ export default defineConfig({
values: [
{
find: /<measurement_id>/g,
replacement: process.env.VITE_APP_GTAG_MEASUREMENT_ID as string,
replacement: process.env.VITE_APP_GTAG_MEASUREMENT_ID as string
},
{
find: /<api_secret>/g,
replacement: process.env.VITE_APP_GTAG_API_SECRET as string,
replacement: process.env.VITE_APP_GTAG_API_SECRET as string
}
]
})
@@ -39,7 +45,7 @@ export default defineConfig({
preload: {
plugins: [externalizeDepsPlugin()],
build: {
minify: 'terser'
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
}
},
renderer: {
@@ -52,11 +58,11 @@ export default defineConfig({
vue(),
UnoCSS({
presets: [presetUno(), presetAttributify(), presetIcons()],
transformers: [transformerDirective()],
transformers: [transformerDirective()]
})
],
build: {
minify: 'terser'
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
}
}
})

View File

@@ -1,6 +1,6 @@
{
"name": "geekgeekrun-ui",
"version": "0.7.1",
"version": "0.9.4",
"description": "Boss 炸弹 - 自动开聊Boss助力每位打工人求职",
"main": "./out/main/index.js",
"author": "geekgeekrun",
@@ -38,13 +38,15 @@
"JSONStream": "^1.3.5",
"diff": "^7.0.0",
"electron-updater": "^6.1.7",
"lodash-es": "^4.17.21",
"minimist": "^1.2.8",
"mitt": "^3.0.1",
"node-machine-id": "^1.1.12",
"pinia": "^3.0.2",
"puppeteer": "20.1.0",
"puppeteer-extra-plugin-stealth": "2.11.2",
"uuid": "^11.1.0"
"uuid": "^11.1.0",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",
@@ -66,12 +68,11 @@
"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",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"normalize.css": "^8.0.1",
"prettier": "^3.2.4",
"sass": "^1.70.0",

View File

@@ -1,7 +1,7 @@
{
"version": "0.7.1",
"buildVersion": 14,
"buildTime": 1748459396794,
"buildHash": "d479687b8ba13c6a7bb81dbade1c29317add46e7",
"version": "0.9.4",
"buildVersion": 20,
"buildTime": 1760377927509,
"buildHash": "51ca5dd71d62de2965fb53cff9d03c0d3552eabb",
"name": "geekgeekrun-ui"
}

View File

@@ -93,7 +93,8 @@ const runAutoChat = async () => {
jobMarkedAsNotSuit: new AsyncSeriesHook(['positionInfoDetail', 'markDetail']),
noPositionFoundForCurrentJob: new SyncHook(),
noPositionFoundAfterTraverseAllJob: new SyncHook(),
errorEncounter: new SyncHook(['errorInfo'])
errorEncounter: new SyncHook(['errorInfo']),
encounterEmptyRecommendJobList: new AsyncSeriesHook(['args'])
}
initPlugins(hooks)

View File

@@ -4,6 +4,10 @@ import {
readStorageFile,
writeStorageFile
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import {
RECOMMEND_JOB_ENTRY_SELECTOR,
USER_SET_EXPECT_JOB_ENTRIES_SELECTOR,
} from '@geekgeekrun/geek-auto-start-chat-with-boss/constant.mjs'
import { setDomainLocalStorage } from '@geekgeekrun/utils/puppeteer/local-storage.mjs'
import {
saveJobInfoFromRecommendPage,
@@ -13,7 +17,7 @@ import {
} from '@geekgeekrun/sqlite-plugin/dist/handlers'
import { initDb } from '@geekgeekrun/sqlite-plugin'
import { getPublicDbFilePath } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { MarkAsNotSuitReason } from '@geekgeekrun/sqlite-plugin/dist/enums'
import { MarkAsNotSuitReason, JobSource } from '@geekgeekrun/sqlite-plugin/dist/enums'
import fs from 'node:fs'
import { Target } from 'puppeteer'
@@ -38,6 +42,49 @@ const attachRequestsListener = async (target: Target) => {
if (!page) {
return
}
async function getCurrentJobSource() {
const methodMap = {
async recommend() {
return await page.evaluate(
({ RECOMMEND_JOB_ENTRY_SELECTOR }) => {
return document.querySelector(RECOMMEND_JOB_ENTRY_SELECTOR).classList.contains('active')
},
{
RECOMMEND_JOB_ENTRY_SELECTOR
}
)
},
async expect() {
return await page.evaluate(
({ USER_SET_EXPECT_JOB_ENTRIES_SELECTOR }) => {
return [...document.querySelectorAll(USER_SET_EXPECT_JOB_ENTRIES_SELECTOR)].some((el) =>
el.classList.contains('active')
)
},
{
USER_SET_EXPECT_JOB_ENTRIES_SELECTOR
}
)
},
async search() {
const elHandle = await page.$(`.page-jobs-main`)
const currentKeyWord = await elHandle?.evaluate((el) => {
return el?.__vue__?.formData?.query
})
return !!currentKeyWord
}
}
for (const [type, func] of Object.entries(methodMap)) {
try {
if (await func()) {
return type
}
} catch (err) {
console.error('encounter error when get job source')
}
}
return null
}
page.on('response', async (response) => {
if (response.url().startsWith('https://www.zhipin.com/wapi/zpgeek/job/detail.json')) {
@@ -81,7 +128,7 @@ const attachRequestsListener = async (target: Target) => {
const reasonCodeToTextMap = await readStorageFile(
'job-not-suit-reason-code-to-text-cache.json'
)
const jobSource = await getCurrentJobSource()
const markDetail = {
markFrom: ChatStartupFrom.ManuallyFromRecommendList,
extInfo: {
@@ -90,12 +137,14 @@ const attachRequestsListener = async (target: Target) => {
text: reasonCodeToTextMap[chosenCode]
}
},
markReason: MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON
markReason: MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON,
jobSource: JobSource[jobSource]
}
gtag('job_marked_as_not_suit', {
markFrom: markDetail.markFrom,
bossActiveTimeDesc: currentJobData?.bossInfo?.activeTimeDesc,
encryptJobId: currentJobData?.jobInfo?.encryptId
encryptJobId: currentJobData?.jobInfo?.encryptId,
jobSource: JobSource[jobSource]
})
if (reasonCodeToTextMap[chosenCode]?.includes('活跃度低')) {
markDetail.markReason = MarkAsNotSuitReason.BOSS_INACTIVE
@@ -131,9 +180,11 @@ const attachRequestsListener = async (target: Target) => {
const currentUserInfo = await page.evaluate(
'document.querySelector(".job-detail-box").__vue__.$store.state.userInfo'
)
const jobSource = await getCurrentJobSource()
gtag('new_chat_startup', {
chatStartupFrom: ChatStartupFrom.ManuallyFromRecommendList,
encryptJobId: currentJobData?.jobInfo?.encryptId
encryptJobId: currentJobData?.jobInfo?.encryptId,
jobSource: JobSource[jobSource]
})
await saveChatStartupRecord(
await dbInitPromise,
@@ -142,7 +193,8 @@ const attachRequestsListener = async (target: Target) => {
encryptUserId: currentUserInfo.encryptUserId
},
{
chatStartupFrom: ChatStartupFrom.ManuallyFromRecommendList
chatStartupFrom: ChatStartupFrom.ManuallyFromRecommendList,
jobSource: JobSource[jobSource]
}
)
} else if (

View File

@@ -107,6 +107,8 @@ export default function initIpc() {
if (hasOwn(payload, 'autoReminder')) {
bossConfig.autoReminder = payload.autoReminder
}
// city
if (hasOwn(payload, 'expectCityList')) {
bossConfig.expectCityList = payload.expectCityList
}
@@ -117,6 +119,47 @@ export default function initIpc() {
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
}
if (hasOwn(payload, 'jobDetailRegExpMatchLogic')) {
bossConfig.jobDetailRegExpMatchLogic = payload.jobDetailRegExpMatchLogic
}
if (hasOwn(payload, 'isSkipEmptyConditionForCombineRecommendJobFilter')) {
bossConfig.isSkipEmptyConditionForCombineRecommendJobFilter =
payload.isSkipEmptyConditionForCombineRecommendJobFilter
}
if (hasOwn(payload, 'jobSourceList')) {
bossConfig.jobSourceList = payload.jobSourceList
}
promiseArr.push(writeConfigFile('boss.json', bossConfig))
if (hasOwn(payload, 'expectCompanies')) {

View File

@@ -2,26 +2,34 @@ import gtag from '.'
export default class GtagPlugin {
apply(hooks) {
hooks.newChatStartup.tap('GtagPlugin', (jobData, { chatStartupFrom }) => {
hooks.newChatStartup.tap('GtagPlugin', (jobData, { chatStartupFrom, jobSource }) => {
gtag('new_chat_startup', {
chatStartupFrom,
encryptJobId: jobData.jobInfo.encryptId
})
})
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
encryptJobId: jobData.jobInfo.encryptId,
jobSource
})
})
hooks.jobMarkedAsNotSuit.tap(
'GtagPlugin',
(jobData, { markFrom, markOp, markReason, jobSource }) => {
gtag('job_marked_as_not_suit', {
markFrom,
markOp,
markReason,
bossActiveTimeDesc: jobData.bossInfo.activeTimeDesc,
encryptJobId: jobData.jobInfo.encryptId,
jobSource
})
}
)
hooks.noPositionFoundForCurrentJob.tap('GtagPlugin', () => {
gtag('no_position_found_for_current_job')
})
hooks.noPositionFoundAfterTraverseAllJob.tap('GtagPlugin', () => {
gtag('no_position_found_after_traverse_all_job')
})
hooks.encounterEmptyRecommendJobList.tap('GtagPlugin', ({ pageQuery }) => {
gtag('encounter_empty_rec_job_list', { pageQuery })
})
}
}

View File

@@ -10,7 +10,7 @@ export function createMainWindow(): BrowserWindow {
mainWindow = new BrowserWindow({
width: 1280,
height: 720,
minWidth: 1024,
minWidth: 1280,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux'

View File

@@ -0,0 +1,357 @@
<template>
<draggable
:model-value="modelValue"
class="list-group"
:component-data="{
tag: 'ul',
type: 'transition-group',
name: !drag ? 'flip-list' : null
}"
:group="{ name: 'parent', put: ['parent'] }"
v-bind="dragOptions"
handle=".drag-handle-wrap"
@update:model-value="emit('update:model-value', $event)"
@start="drag = true"
@end="
() => {
drag = false
setTimeout(() => {
gtagRenderer('job-source-dragged', {
sourceOrder: modelValue?.map((it) => JobSource[it.type]).join(','),
enabledSourceOrder: modelValue
?.filter?.((it) => !!it.enabled)
?.map((it) => JobSource[it.type])
?.join(',')
})
}, 50)
}
"
>
<template #item="{ element }">
<li class="list-group-item">
<div flex flex-items-center>
<span class="drag-handle-wrap">
<span class="drag-handle" />
</span>
<span class="inner-content">
<div flex w-full flex-1>
<template
v-if="
element.type === 'search' &&
(!element?.children?.length ||
element?.children?.every((it) => !(it.enabled && !!it.keyword?.trim())))
"
>
<el-switch
model-value="false"
mr10px
disabled
active-text="启用"
inactive-text="禁用"
inline-prompt
@click="
() => {
if (!element?.children?.length) {
Message.info({
message: '请添加一个要搜索的关键词',
grouping: true
})
} else if (
element?.children?.every((it) => !(it.enabled && !!it.keyword?.trim()))
) {
Message.info({
message: '请启用一个要搜索的关键词',
grouping: true
})
}
gtagRenderer('job-source-switch-ph-clicked', { type: element.type })
}
"
/>
{{ element.label }}
</template>
<template v-else>
<el-switch
v-model="element.enabled"
mr10px
active-text="启用"
inactive-text="禁用"
inline-prompt
@change="
(v) => gtagRenderer('job-source-switch-changed', { type: element.type, v })
"
/>
{{ element.label }}
</template>
</div>
<div v-if="element.type === 'search'">
<div flex flex-items-center>
<span color-orange align-self-end mr10px>
{{ getSearchSourceTipText(element) }}
</span>
<el-button
p-0
h-fit
type="text"
@click="
() => {
addSearchKeyword(element)
gtagRenderer('job-source-search-kw-added', {
kwListLength: element.children?.length
})
}
"
>添加关键词</el-button
>
</div>
</div>
</span>
</div>
<div v-if="element.type === 'search' && element?.children?.length">
<draggable
v-model="element.children"
class="list-group"
:component-data="{
tag: 'ul',
type: 'transition-group',
name: !drag ? 'flip-list' : null
}"
:group="{ name: 'child', put: ['child'] }"
v-bind="dragOptions"
handle=".drag-handle-wrap"
@start="drag = true"
@end="
() => {
drag = false
setTimeout(() => {
gtagRenderer('job-source-search-kw-dragged', {
kwListLength: element.children?.length
})
}, 50)
}
"
>
<template #item="{ element: searchItem, index }">
<li class="list-group-item">
<div flex flex-items-center>
<span class="drag-handle-wrap">
<span class="drag-handle" />
</span>
<span class="inner-content">
<div flex w-full>
<el-switch
v-if="element.enabled && searchItem.keyword?.trim()"
v-model="searchItem.enabled"
mr10px
active-text="启用"
inactive-text="禁用"
inline-prompt
@change="(v) => gtagRenderer('job-source-search-kw-switch-changed', { v })"
/>
<el-switch
v-else
disabled
:model-value="false"
mr10px
active-text="启用"
inactive-text="禁用"
inline-prompt
@click="
() => {
if (!searchItem.keyword?.trim()) {
Message.info({
message: '该条关键词为空,请输入要搜索的关键词',
grouping: true
})
} else if (!element.enabled) {
Message.info({
message: '请启用“通过搜索找到的职位”来源',
grouping: true
})
}
gtagRenderer('job-source-search-kw-switch-ph-clicked')
}
"
/>
<el-input
v-model="searchItem.keyword"
maxlength="100"
@blur="
() => {
searchItem.keyword = searchItem.keyword?.trim() ?? ''
gtagRenderer('job-source-search-kw-input-blurred', {
contentLength: searchItem.keyword?.length
})
}
"
/>
</div>
<el-button
p-0
ml-10px
h-fit
type="danger"
link
@click="
() => {
removeSearchKeywordByIndex(element, index)
gtagRenderer('job-source-search-kw-removed', {
itemIndex: index,
contentLength: searchItem.keyword?.length
})
}
"
>删除</el-button
>
</span>
</div>
</li>
</template>
</draggable>
</div>
</li>
</template>
</draggable>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import draggable from 'vuedraggable'
import { ElMessage as Message } from 'element-plus'
import { gtagRenderer } from '@renderer/utils/gtag'
import { JobSource } from '@geekgeekrun/sqlite-plugin/src/enums'
const props = defineProps({
modelValue: {
type: Array
}
})
const setTimeout = globalThis.setTimeout.bind(globalThis)
const emit = defineEmits(['update:model-value'])
const drag = ref(false)
const dragOptions = computed(() => {
return {
animation: 200,
disabled: false,
ghostClass: 'ghost'
}
})
function addSearchKeyword(item) {
if (!item.children) {
item.children = []
}
item.enabled = true
item.children.push({
type: 'search-kw',
enabled: true,
keyword: ''
})
}
function removeSearchKeywordByIndex(item, index) {
item.children?.splice(index, 1)
}
const searchKeywordCountMap = computed(() => {
const target = props.modelValue?.find((it) => it.type === 'search' && it.enabled)
if (!target || !target?.children?.length) {
return {}
}
const map = {}
target.children?.forEach((it) => {
if (!it.keyword) {
return
}
if (!map[it.keyword]) {
map[it.keyword] = 0
}
map[it.keyword]++
})
return map
})
const getSearchSourceTipText = (element) => {
if (!element?.children?.length) {
return `添加一个关键词后方可启用->`
}
const seg: string[] = []
if (element.enabled) {
if (element.children?.every((it) => !(it.enabled && !!it.keyword?.trim()))) {
seg.push(`启用下方任一非空项后方可启用`)
} else if (element.children?.some((it) => it.enabled && !it.keyword?.trim())) {
seg.push(`空项会被跳过`)
}
if (Object.values(searchKeywordCountMap.value).some((n) => n > 1)) {
seg.push(`重复项仅取第一个启用的项`)
}
}
return seg.join('')
}
</script>
<style lang="scss" scoped>
.ghost {
opacity: 0.5;
filter: blur(2px);
}
.list-group {
line-height: 32px;
min-height: 20px;
.list-group {
margin-left: 20px;
}
.list-group-item {
background-color: #fff;
list-style: none;
display: flex;
flex-direction: column;
padding-top: 6px;
padding-bottom: 6px;
padding-right: 10px;
border: 1px solid var(--el-card-border-color);
margin-top: 6px;
margin-bottom: 6px;
box-shadow: var(--el-box-shadow-light);
border-radius: 4px;
.inner-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
::v-deep(.el-checkbox) {
.el-checkbox__label {
width: 100%;
}
}
}
.drag-handle-wrap {
display: flex;
justify-content: flex-end;
width: 25px;
height: 1em;
cursor: grab;
margin-right: 4px;
.drag-handle {
width: 20px;
background-image: linear-gradient(
90deg,
transparent 35%,
var(--el-card-border-color) 35%,
var(--el-card-border-color) 45%,
transparent 45%,
transparent 55%,
var(--el-card-border-color) 55%,
var(--el-card-border-color) 65%,
transparent 65%,
transparent 100%
);
}
}
}
}
</style>

View File

@@ -109,8 +109,14 @@ import { ElForm, ElMessage } from 'element-plus'
import { ref, onUnmounted, onMounted } from 'vue'
import { checkCookieListFormat } from '../../../../common/utils/cookie'
import { useRouter } from 'vue-router'
import { gtagRenderer } from '@renderer/utils/gtag'
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
const gtagRenderer = (name, params?: object) => {
return baseGtagRenderer(name, {
scene: 'cookie-assistant',
...params
})
}
const router = useRouter()
const cookieInvalid = ref(false)

View File

@@ -1,5 +1,5 @@
<template>
<div class="first-run-readme" ref="componentRootEl">
<div ref="componentRootEl" class="first-run-readme">
<div class="first-run-readme__inner">
<div class="readme-title">欢迎使用GeekGeekRun祝您求职顺利~</div>
<div class="readme-desc">
@@ -12,25 +12,32 @@
>
<ElCheckbox :label="0" :class="[unreadItemsAfterClickSubmit[0] ? 'unread' : '']">
本程序从某种程度上说属于辅助工具<el-link
@click="
electron.ipcRenderer.send(
'open-external-link',
'https://about.zhipin.com/agreement/'
)
color-blue
@click.stop.prevent="
() => {
electron.ipcRenderer.send(
'open-external-link',
'https://about.zhipin.com/agreement/'
)
gtagRenderer('view_boss_agreement_clicked')
}
"
>Boss直聘用户协议</el-link
>相关条款相违背
根据该条款如果一些非正常用户行为被风控监测到您需要承受包括不仅限于<b
class="color-red"
>账号被强制退出登录</b
><b class="color-red">账号被限制使用</b><b class="color-red">账号被封禁</b
>2023年3月版相关条款相违背您在注册Boss直聘时已签署过这一条款根据该条款
<i>用户的平台使用义务</i><i>违约责任</i>
章节如果一些非正常用户行为被风控监测到您需要承受包括不仅限于<b class="color-red"
>账号被强制退出登录账号被限制使用账号被封禁</b
>等对您不利的风险因此使用本程序即意味着<b class="color-red">您愿意接受以上风险</b
>且如果相关风险发生<b class="color-red">您需要自行承担相关后果</b><b
class="color-red"
>本程序不负责</b
>
>且如果相关风险发生<b class="color-red">您需要自行承担相关后果本程序不负责</b>
</ElCheckbox>
<ElCheckbox :label="1" :class="[unreadItemsAfterClickSubmit[1] ? 'unread' : '']">
本程序需要存储您的登录凭据即Cookie来模拟您在Boss直聘上开聊Boss的行为本程序仅会把您的Cookie存储在本地并在您访问Boss直聘时将其传输到Boss直聘<b
class="color-red"
>不会泄露给第三方</b
>也不会进行除自动开聊Boss以外的行为<b class="color-red">请勿向他人泄漏您的Cookie</b
>
</ElCheckbox>
<ElCheckbox :label="2" :class="[unreadItemsAfterClickSubmit[2] ? 'unread' : '']">
本程序会通过尽可能模仿用户行为来规避相关风险但并不能保证可以完全规避建议您使用本程序时<b
class="color-red"
>注意节制</b
@@ -38,39 +45,58 @@
>注册一个本程序专用的新的Boss直聘账号</b
>进行求职
</ElCheckbox>
<ElCheckbox :label="2" :class="[unreadItemsAfterClickSubmit[2] ? 'unread' : '']">
本程序原理是模拟用户在Boss直聘网页上进行点击操作Boss直聘网站每过一段时间会发生改版且有可能包含A/B实验这将导致本程序相关脚本失效此时您可以点击程序左下角进行反馈
</ElCheckbox>
<ElCheckbox :label="3" :class="[unreadItemsAfterClickSubmit[3] ? 'unread' : '']">
您的雇主可能会对您的计算机终端或网络进行监控从而审计跟踪您的行为建议您<b
本程序原理是模拟用户在Boss直聘网页上寻找关键元素并进行点击操作Boss直聘网站经常<b>发生改版</b>且有可能<b>包含A/B实验</b>这将导致本程序相关脚本失效典型表现为本程序运行到某一步骤后<b
class="color-red"
>不要在您雇主提供的计算机终端或网络上使用本程序</b
>
>浏览器重复闪退重新启动</b
>如果您在使用过程中遇上程序未按照预期执行的情况请点击程序左下角进行反馈
</ElCheckbox>
<ElCheckbox :label="4" :class="[unreadItemsAfterClickSubmit[4] ? 'unread' : '']">
本程序需要存储您的登录凭据即Cookie来模拟您在Boss直聘上开聊Boss的行为本程序仅会把您的Cookie存储在本地并在您访问Boss直聘时将其传输到Boss直聘<b
class="color-red"
>不会泄露给第三方</b
>也不会进行除自动开聊Boss以外的行为<b class="color-red">请勿向他人泄漏您的Cookie</b
您所在公司可能会对您的计算机终端或网络进行<b color-red>监控</b>从而<b color-red
>审计跟踪</b
>您的行为上级 / HRBP 团队可能会从 IT
团队处获取到监控数据从而了解团队成员离职倾向如果您不希望上级 / HRBP
团队了解到您正在求职建议您<b color-red
>不要在您所在公司提供的计算机终端或网络上使用本程序</b
>
</ElCheckbox>
<ElCheckbox :label="5" :class="[unreadItemsAfterClickSubmit[5] ? 'unread' : '']">
本程序<b class="color-red"
>不会上报您的隐私诸如能够识别出您身份的信息不会向您的雇主报告您的求职行为</b
>如果您在使用过程中遇上程序错误您可以点击程序左下角进行反馈
本程序尊重您的隐私<b color-red
>不会上报能够识别出您身份的信息不会参与任何钓鱼活动不会向您所在公司及上级 / HRBP
团队报告您的求职行为不会向猎头公司泄露您的信息</b
>
</ElCheckbox>
<ElCheckbox :label="6" :class="[unreadItemsAfterClickSubmit[6] ? 'unread' : '']">
<b class="color-red">本程序不对您的求职过程与结果负责</b
>为您开聊的职位均在Boss直聘上发布<b class="color-red">自行甄别为您开聊的公司</b
><b class="color-red">认真决定是否参加面试</b><b class="color-red">慎重选择Offer</b
本程序<b class="color-red">没有内置任何付费功能</b><b class="color-red"
>下载使用是免费的</b
>任何人可以<b class="color-red">免费获得免费使用</b><b class="color-red"
>作者没有利用本程序赚到过任何收入</b
>如果您是从GitHub以外的地方付费后购买的本程序或您被提示必须付费后才能使用本程序<b
class="color-red"
>那您大概率被骗了</b
>或者<b class="color-red">您下载到了本程序修改版</b><b class="color-red"
>本程序对此概不负责请勿找作者商讨退款售后事宜相关事宜请咨询卖方</b
>
</ElCheckbox>
<ElCheckbox :label="7" :class="[unreadItemsAfterClickSubmit[7] ? 'unread' : '']">
请在Boss直聘上自行<b class="color-red">屏蔽您不期望投递的公司</b>
本程序<b class="color-red">不对您的求职过程与结果负责</b
>为您开聊的职位均在Boss直聘上发布职位信息真实性由Boss直聘负责<b
class="color-red"
>自行甄别为您开聊的公司认真决定是否参加面试慎重选择Offer</b
>
</ElCheckbox>
<ElCheckbox :label="8" :class="[unreadItemsAfterClickSubmit[8] ? 'unread' : '']">
本程序经历过了多次测试理论上来说大部分情况下可以正常运行如果您有顾虑建议通过VMWare
WorkStation/FusionVirtualBoxHyper-V/Windows沙盒等虚拟化技术运行本程序如发生问题请点击程序左下角进行反馈
请在Boss直聘上自行<b class="color-red">屏蔽您不期望投递的公司</b
>如果您不希望您当前公司其它具有招聘账号的员工看到您在Boss直聘上活跃<b
class="color-red"
>在Boss直聘上屏蔽当前公司及与之关联的公司</b
>
</ElCheckbox>
<ElCheckbox :label="9" :class="[unreadItemsAfterClickSubmit[9] ? 'unread' : '']">
本程序经历过了多次测试理论上来说大部分情况下可以正常运行但可能也会出现测试用例覆盖不到位导致程序不按预期运行的情况如果您有顾虑建议通过
VMware Workstation / FusionOracle VirtualBoxMicrosoft Hyper-V
等虚拟化技术运行本程序如果您在使用过程中遇上程序未按照预期执行的情况请点击程序左下角进行反馈
</ElCheckbox>
</ElCheckboxGroup>
</article>
@@ -86,26 +112,36 @@
<script lang="ts" setup>
import { ElCheckbox, ElCheckboxGroup, ElMessage } from 'element-plus'
import { ref, onMounted, onBeforeMount } from 'vue';
import { gtagRenderer } from '@renderer/utils/gtag'
import { ref, onMounted, onBeforeMount } from 'vue'
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
const gtagRenderer = (name, params?: object) => {
return baseGtagRenderer(name, {
scene: 'first-run-agreement',
...params
})
}
const electron = window.electron
const readmeItemCheckStatusList = ref<number[]>([])
const handleCancel = () => {
const handleCancel = async () => {
gtagRenderer('cancel_clicked')
await sleep(500)
electron.ipcRenderer.invoke('exit-app-immediately')
}
const unreadItemsAfterClickSubmit = ref<Record<number, true>>({})
const handleSubmit = () => {
gtagRenderer('submit_clicked')
const COUNT = 9
const COUNT = 10
if (readmeItemCheckStatusList.value.length !== COUNT) {
gtagRenderer('agreement_not_finish_read_tip_displayed')
ElMessage.warning(
`您还有${COUNT - readmeItemCheckStatusList.value.length}条没有读完,读完就打勾标记一下吧`
)
ElMessage.warning({
message: `您还有${COUNT - readmeItemCheckStatusList.value.length}条没有读完,读完就打勾标记一下吧`,
grouping: true
})
unreadItemsAfterClickSubmit.value = {}
for (let i = 0; i < COUNT; i++) {
if (!readmeItemCheckStatusList.value.includes(i)) {
@@ -127,13 +163,10 @@ const handleReadmeItemCheckStatusListChange = (value: number[]) => {
const componentRootEl = ref<HTMLElement>()
onMounted(() => {
const ro = new ResizeObserver(() => {
electron.ipcRenderer.send(
'update-window-size',
{
width: componentRootEl.value!.offsetWidth,
height: componentRootEl.value!.offsetHeight,
}
)
electron.ipcRenderer.send('update-window-size', {
width: componentRootEl.value!.offsetWidth,
height: componentRootEl.value!.offsetHeight
})
})
ro.observe(componentRootEl.value!)
onBeforeMount(() => {

View File

@@ -254,7 +254,7 @@ import {
} from 'element-plus'
import { ArrowUp, ArrowDown, Delete } from '@element-plus/icons-vue'
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import { gtagRenderer } from '@renderer/utils/gtag'
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
import { SINGLE_ITEM_DEFAULT_SERVE_WEIGHT } from '../../../../common/constant'
import { v4 as uuid } from 'uuid'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
@@ -267,6 +267,13 @@ interface LlmConfigItem {
enabled: true
}
const gtagRenderer = (name, params?: object) => {
return baseGtagRenderer(name, {
scene: 'llm-config',
...params
})
}
function getNewConfigItem(): LlmConfigItem {
return {
id: uuid(),

View File

@@ -11,8 +11,23 @@
<div v-else>
<div>当前未选择任何期望城市将不会按照城市进行筛选</div>
</div>
<div>
<el-button size="small" type="primary" @click="isDialogVisible = true">选择城市</el-button>
<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"
@@ -105,6 +120,7 @@
<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: {
@@ -136,23 +152,29 @@ for (const group of cityGroup) {
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>

View File

@@ -42,6 +42,14 @@
<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="工作经验" />
@@ -209,7 +217,9 @@ const markReasonTopicMap = {
[MarkAsNotSuitReason.BOSS_INACTIVE]: 'Boss不活跃',
[MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON]: '手动标记不合适',
[MarkAsNotSuitReason.JOB_NOT_SUIT]: '职位不合适',
[MarkAsNotSuitReason.JOB_CITY_NOT_SUIT]: '工作地不合适'
[MarkAsNotSuitReason.JOB_CITY_NOT_SUIT]: '工作地不合适',
[MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT]: '工作经验不合适',
[MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT]: '薪资不合适'
}
function formatMarkReason(row: VMarkAsNotSuitLog) {
@@ -241,6 +251,7 @@ function formatMarkReason(row: VMarkAsNotSuitLog) {
.filter(Boolean)
.join('\n')
}
case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT:
case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: {
const extInfo = (() => {
try {
@@ -253,6 +264,21 @@ function formatMarkReason(row: VMarkAsNotSuitLog) {
.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 ''
}

View File

@@ -231,10 +231,15 @@ import {
RECHAT_CONTENT_SOURCE,
RECHAT_LLM_FALLBACK
} from '../../../../common/enums/auto-start-chat'
import { gtagRenderer } from '@renderer/utils/gtag'
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
import mittBus from '../../utils/mitt'
import { QuestionFilled } from '@element-plus/icons-vue'
const gtagRenderer = (name, params?: object) => {
return baseGtagRenderer(name, {
scene: 'rnrr-config',
...params
})
}
const router = useRouter()
const formContent = ref({
autoReminder: {

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

View File

@@ -322,11 +322,18 @@
import { ElForm, ElButton, ElAlert, ElMessageBox } from 'element-plus'
import { ref, onMounted, computed } from 'vue'
import { ArrowUp, ArrowDown, Delete, Plus } from '@element-plus/icons-vue'
import { gtagRenderer } from '@renderer/utils/gtag'
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
import { type ResumeContent, resumeContentEnoughDetect } from '../../../../common/utils/resume'
const formRef = ref<InstanceType<typeof ElForm>>()
const gtagRenderer = (name, params?: object) => {
return baseGtagRenderer(name, {
scene: 'resume-editor',
...params
})
}
const getEmptyFormContent = () => {
const o: any = {
expectJob: '',

38
pnpm-lock.yaml generated
View File

@@ -129,6 +129,9 @@ importers:
electron-updater:
specifier: ^6.1.7
version: 6.1.7
lodash-es:
specifier: ^4.17.21
version: 4.17.21
minimist:
specifier: ^1.2.8
version: 1.2.8
@@ -150,6 +153,9 @@ importers:
uuid:
specifier: ^11.1.0
version: 11.1.0
vuedraggable:
specifier: ^4.1.0
version: 4.1.0(vue@3.4.15)
devDependencies:
'@electron-toolkit/eslint-config':
specifier: ^1.0.2
@@ -209,8 +215,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
@@ -223,9 +229,6 @@ importers:
js-yaml:
specifier: ^4.1.0
version: 4.1.0
lodash-es:
specifier: ^4.17.21
version: 4.17.21
normalize.css:
specifier: ^8.0.1
version: 8.0.1
@@ -3085,6 +3088,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==}
@@ -3360,8 +3368,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:
@@ -3373,7 +3381,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
@@ -4562,7 +4570,6 @@ packages:
/lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: true
/lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
@@ -5816,6 +5823,10 @@ packages:
ip: 2.0.0
smart-buffer: 4.2.0
/sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
dev: false
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
@@ -6620,6 +6631,15 @@ packages:
'@vue/shared': 3.4.15
typescript: 5.3.3
/vuedraggable@4.1.0(vue@3.4.15):
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
peerDependencies:
vue: ^3.0.1
dependencies:
sortablejs: 1.14.0
vue: 3.4.15(typescript@5.3.3)
dev: false
/web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}