牛牛查求职助手-招聘网站信息增强工具

在招聘网站职位列表页每个职位添加公司工商信息(天眼查、爱企查、企查查)查询功能,增加额外的职位首发/更新时间,职位详情。支持前程无忧、BOSS直聘、牛企直聘网站。

// ==UserScript==
// @name         牛牛查求职助手-招聘网站信息增强工具
// @namespace    http://c1gstudio.com/
// @version      1.2
// @homepage     https://blog.c1gstudio.com/
// @supportURL   https://github.com/andychu46/niuniuchajobhunting
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAACeElEQVQ4jW2TS0hUYRTHf+d6aWZ8TDa+Hzk+kkQqCUJbFGXYIjCzNgVCSBG0aeEi27Vo12NR5KJFIEFaG4PouYnAkh5WFBET5WNoJnMcxvHF6Dhz79fi3hlH8GzO4Tvf/3yH////iVKmAgAB7BJhfagN+latKRRKqYxLG4HJAFpnFgZ0QVCY6amxeJxsh5P+d494HfmChrC/cDdn954gFl/GtcmBiAZib5A5fWYuQm//VfyhILfGBvhVM4Wv5i83xweZDAXp7b9OeH4WUIgNFaVMpZSJSBbfJnwYpsmdlw9BwJ2bAwgLi4uAcP7IKTSEptoGCyyCDgpE+D3l50dgjMcfXnGt+yJlnkIC4X+AsLWolKlImEv3btDR0kaOK5v6cq/FgcWJQhBM02TPth14i8u5MthHT+cZ7r4fYnj0K07NQW1lFaZpZNAsiKkMhVKICPOxJfqeDlBdXIGu69z//oTYPqE+z8u8sYQvOM5lZzfHm9tAQNJMICgFblcuFVuK6TrYTn2pF39RiMb8OnpKurhQeBJvRRnPIyOISFpdTeyFRDREBE3L4k94msaqOkDxc8XP5Mo0geQM4eTcmivE8o6uULYxTEQET56bB8PPmAgFiEfjeJryeBP7iAK0qHCs5ECGEyVlJCsWYkusJhO07mzmdGsHyaHbVIwVMbLyGZe4OFfUTmxxOe1OlVbBjoSRJLIQZTTo4/D2Fjp3tXK05RCpLV98estsPLrO2OsGeHLd5OdvZnUlSENlNWUFxSmxACHb4cRZUGJ/BwFJy2iuuVpMq5l6RTSbHytbilkbiWg2B5LxJRBExKZIUlPWshISiQSGYaDrOv8BNCz+u1A+hJcAAAAASUVORK5CYII=
// @description  在招聘网站职位列表页每个职位添加公司工商信息(天眼查、爱企查、企查查)查询功能,增加额外的职位首发/更新时间,职位详情。支持前程无忧、BOSS直聘、牛企直聘网站。
// @author       c1gstudio
// @match        *://we.51job.com/*
// @match        *://www.zhipin.com/*
// @match        *://campus.niuqizp.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @run-at       document-idle
// @license      MIT
// @copyright 2025, awpb46 (https://openuserjs.org/users/awpb46)
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==

(function() {
    'use strict';

    // ==================== 常量定义 ====================
    const SCRIPT_NAME = '牛牛查求职助手';
    const DEBUG = false;
    
    // 日期颜色配置
    const DATE_COLORS = {
        fresh: '#4CAF50',    // 7天内-绿色
        recent: '#2196F3',   // 14天内-蓝色  
        normal: '#FFC107',   // 2个月内-黄色
        old: '#F44336',      // 3个月内-红色
        expired: '#9E9E9E'   // 3个月以上-灰色
    };
    
    // 查询服务配置
    const QUERY_SERVICES = [
        { name: '🔍 百度', url: 'https://www.baidu.com/s?wd=' },
        { name: '👁️ 天眼查', url: 'https://www.tianyancha.com/search?key=' },     
        { name: '📊 爱企查', url: 'https://aiqicha.baidu.com/s?t=0&q=' },           
        { name: '🏢 企查查', url: 'https://www.qcc.com/web/search?key=' }
    ];

    
    // ==================== 公司名单数据库 ====================
    const COMPANY_BLACKLISTS = {
        // 诈骗公司名单,请自行完善
        scam: {
            name: '诈骗',
            emoji: '⚠️',
            color: '#FF5722', // 红色警告
            companies: [
                '华安', '虚假投资', '假冒银行',
                '假冒政府', '假冒公安', '假冒快递', '假冒客服', '刷单诈骗',
                '贷款诈骗', '兼职诈骗', '投资诈骗', '理财诈骗', '炒股诈骗',
                '外汇诈骗', '期货诈骗', '比特币诈骗', '虚拟货币诈骗',
                '网恋诈骗', '交友诈骗', '婚恋诈骗', '征婚诈骗'
            ]
        },
        
        // 外包公司名单,由AI生成,请自定义
        outsourcing: {
            name: '外包', 
            emoji: '🔄',
            color: '#FF9800', // 橙色提醒
            companies: [
            "软通动力", "中软国际", "中科软文", "博彦科技", "易思博", 
            "润和软件", "佰钧成", "睿服科技", "亿达信", "微创软件", 
            "招银云创", "拓维云创", "华为od", "德科信息", "外企德科", 
            "深劳人力", "拓保软件", "法本信息", "腾云悦智", "腾云忆想", 
            "汇合发展", "神州信息", "网新新思", "东华软件", "音佇自联想云领", 
            "金证科技", "平安金服", "前海金信", "中盛瑞达", "东软集团", 
            "格创东智", "安韦尔", "奥博特", "汉克时代", "易宝软件", 
            "讯方技术股份", "四川准达", "诚迈科技", "柯莱特集团", "联愬利泰", 
            "神州新桥", "神州数码", "科锐国际麦亚信", "青橄榄", "马衡达", 
            "京北方", "四方精创", "宇信科技", "橙色魔方", "华通科技", 
            "文思海辉", "海隆软件", "启明软件", "神州信息", "神州数码", 
            "神州泰岳", "神州通誉", "亚信联创", "法本信息", "上海中和", 
            "纬创软件", "中软国际", "软通动力", "柯莱特", "上海新致软件", 
            "上海晟欧", "浪潮软件", "北京汉克时代", "博彦科技", "大连华信", 
            "信华信", "华信泛亚信息技术", "同方鼎欣", "易思博", "迪原创新", 
            "中讯软件", "睿服科技", "晟峰软件", "卓越际联", "杭州颐和科技", 
            "恒生电子", "联和利泰", "阳光雨露", "宏智科技", "华道数据处理", 
            "北京中恒博瑞", "江苏欧索软件", "经纬国际", "北京护航", "杭州七凌科技", 
            "北京华胜天成", "北京尖峰", "北京开运联合", "亿达信息", "立思辰科技", 
            "赛迪通呼叫中心", "盛安德", "泛微软件", "新聚思", "在信汇通", 
            "中科创达", "博朗软件", "创博国际", "华拓数码", "大宇宙信息", 
            "大展科技", "第一线安莱", "东南融通", "福瑞博德", "富基融通", 
            "富士通信息", "某德软件", "广东迅维", "九城关贸", "武汉佰钧", 
            "开运联合", "联迪恒星", "联合信息", "凌志软件", "普联软件", 
            "千方科技", "日电卓越", "赛科斯", "北明全程物流", "中盈蓝海", 
            "上海海隆", "杭州斯凯网络", "四川汉科", "索迪斯", "通动力信息", 
            "通邮集团", "万国数据服务", "上海微创软件", "西安诺赛软件", "西安炎兴", 
            "新宇软件", "信必优", "信雅达", "药明康德", "音泰思", 
            "英极软件开发", "北京永新视博", "北京灵信互动", "中网在线", "中创软件", 
            "宇信易诚", "浙大网新"
            ]
        },
        
        // 培训公司名单, 由AI生成,请自定义
        training: {
            name: '培训', 
            emoji: '📚',
            color: '#2196F3', // 蓝色信息
            companies: [
                '达内', '传智播客', '黑马程序员', '尚硅谷', '千锋教育',
                '动力节点', '马士兵教育', '咕泡学院', '拉勾教育', '开课吧',
                '极客时间', '慕课网', '实验楼', 'CSDN学院', '51CTO学院',
                '腾讯课堂', '网易云课堂', '中公教育', '华图教育', '粉笔教育',
                '高顿教育', '尚德机构', '环球网校', '新东方在线', '学而思网校',
                '猿辅导', '作业帮', 'VIPKID', '掌门1对1', '跟谁学',
                '火花思维', '豌豆思维', '编程猫', '小码王', '核桃编程',
                '优就业', 'IT培训', '软件培训', '编程培训', '计算机培训',
                '互联网培训', 'Java培训', 'Python培训', '前端培训', 'UI培训',
                '测试培训', '运维培训', '大数据培训', '人工智能培训', '区块链培训'
            ]
        },
        
        // 自定义警告名单(可根据需要添加)
        custom: {
            name: '自定义',
            emoji: '🚨',
            color: '#9C27B0', // 紫色自定义
            companies: [
                // 可以根据个人经验添加需要注意的公司
                '996公司', '福报公司', '无薪加班', '克扣工资', '拖欠工资',
                '无五险一金', '试用期陷阱', '霸王条款', '违法解约',
                '虚假宣传', '夸大职位', '低薪高要求', '频繁加班'
            ]
        }
    };


    // 网站配置对象,存储不同网站的选择器和API信息
    const siteConfigs = {
        'we.51job.com': {
            companyNameSelector: '.joblist-item-bot .bl > a',
            jobListSelector: '.joblist-item > div',
            jobTitleSelector: '.joblist-item-top > .jname',
            hrInfoSelector: '.chat',
            apiPatterns: [
                '/api/job/search-pc'
            ],
            waitForElement: 'div.joblist',
            enableUrlChangeMonitoring: true,
            companyNameFromElement: function(el) { return el.textContent.trim(); }
        },
        'www.zhipin.com': {
            companyNameSelector: '.job-card-footer > .boss-info > .boss-name',
            jobListSelector: '.job-card-box',
            jobTitleSelector: '.job-name',
            hrInfoSelector: '.info-company .info-public, .job-author .name',
            apiPatterns: [
                '/wapi/zpgeek/search/joblist.json',
                '/wapi/zpgeek/pc/recommend/job/list.json'  
            ],
            waitForElement: 'div.recommend-result-job',
            enableUrlChangeMonitoring: true,
            companyNameFromElement: function(el) { return el.textContent.trim(); }
        },
        'campus.niuqizp.com': {
            companyNameSelector: '.detail-title-campus',
            jobListSelector: '',
            jobTitleSelector: '.job-name',
            hrInfoSelector: '',
            apiPatterns: [],
            waitForElement: '.job-detail',
            detailContentSelector: '.job-detail',
            detailMoreLayer: '.job-meta',
            detailCompanyName: '.detail-title-campus',
            detailOutLink: 'a.jshow_link_c',
            enableUrlChangeMonitoring: false,
            companyNameFromElement: function(el) { return el.textContent.trim(); }
        }        
    };

    // ==================== 初始化配置 ====================
    const initConfig = {
        initialLoadDelay: 1500,     // 初始加载延迟(ms)
        enableApiInterception: true, // 是否启用API拦截
        maxInitialRetries: 3,       // 最大初始重试次数
        retryInterval: 1000,        // 重试间隔(ms)
        showTipButton: false         // 是否显示功能提示按钮
    };
    
    // ==================== 全局变量 ====================
    const currentHost = window.location.hostname;
    const config = siteConfigs[currentHost];
    let apiData = [];

    const logger = {
        log: (...args) => DEBUG && console.log(`[${SCRIPT_NAME}]`, ...args),
        warn: (...args) => console.warn(`[${SCRIPT_NAME}]`, ...args),
        error: (...args) => console.error(`[${SCRIPT_NAME}]`, ...args)
    };

    if (!config) {
        logger.warn('未找到匹配的配置,当前主机名:', currentHost);
        return;
    }

    logger.log('脚本启动,当前主机名:', currentHost);

    if (initConfig.enableApiInterception && config.apiPatterns?.length) {
        interceptAPIRequests();
    }

    // ==================== 样式定义 ====================
    GM_addStyle(`
        .niuniu_company-query-layer{display:inline-flex;align-items:center;margin-left:12px;position:relative;z-index:99}
        .niuniu_company-query-btn{display:inline-flex;align-items:center;justify-content:center;padding:6px 12px;margin:0 4px;border-radius:6px;background:linear-gradient(135deg,#4CAF50 0%,#45a049 100%);color:white;text-decoration:none;font-size:12px;font-weight:500;cursor:pointer;border:none;min-width:60px;transition:all 0.3s cubic-bezier(0.4,0,0.2,1);box-shadow:0 2px 4px rgba(76,175,80,0.3)}
        .niuniu_company-query-btn:hover{background:linear-gradient(135deg,#45a049 0%,#3d8b40 100%);transform:translateY(-1px);box-shadow:0 4px 8px rgba(76,175,80,0.4)}
        .niuniu_company-query-btn:active{transform:translateY(0);box-shadow:0 2px 4px rgba(76,175,80,0.3)}
        .niuniu_query-popup{position:absolute;top:100%;left:0;min-width:120px;background:white;border:1px solid #e0e0e0;border-radius:8px;padding:8px 0;box-shadow:0 8px 24px rgba(0,0,0,0.15);z-index:10000;margin-top:4px;opacity:0;visibility:hidden;transform:translateY(-10px);transition:all 0.3s cubic-bezier(0.4,0,0.2,1)}
        .niuniu_query-popup.show{opacity:1;visibility:visible;transform:translateY(0)}
        .niuniu_query-popup a{display:flex;align-items:center;padding:8px 16px;color:#333;text-decoration:none;font-size:13px;transition:all 0.2s ease;border-bottom:1px solid #f5f5f5}
        .niuniu_query-popup a:last-child{border-bottom:none}
        .niuniu_query-popup a:hover{background-color:#f8f9fa;color:#4CAF50}
        .niuniu_company-tag{display:inline-flex;align-items:center;padding:6px 12px;border-radius:12px;color:white;font-size:11px;font-weight:600;margin-right:8px;white-space:nowrap;text-shadow:0 1px 2px rgba(0,0,0,0.2);animation:companyTagFadeIn 0.3s ease-in-out}
        .niuniu_company-tag.scam{background:linear-gradient(135deg,#FF5722 0%,#D32F2F 100%);border:1px solid #D32F2F;box-shadow:0 2px 4px rgba(255,87,34,0.3)}
        .niuniu_company-tag.outsourcing{background:linear-gradient(135deg,#FF9800 0%,#F57C00 100%);border:1px solid #F57C00;box-shadow:0 2px 4px rgba(255,152,0,0.3)}
        .niuniu_company-tag.training{background:linear-gradient(135deg,#2196F3 0%,#1976D2 100%);border:1px solid #1976D2;box-shadow:0 2px 4px rgba(33,150,243,0.3)}
        .niuniu_company-tag.custom{background:linear-gradient(135deg,#9C27B0 0%,#7B1FA2 100%);border:1px solid #7B1FA2;box-shadow:0 2px 4px rgba(156,39,176,0.3)}
        @keyframes companyTagFadeIn{from{opacity:0;transform:scale(0.8)}to{opacity:1;transform:scale(1)}}
        .niuniu_job-info-layer{display:flex;flex-direction:column;gap:8px;padding:8px;background:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);border-radius:8px;margin:8px 0;font-size:13px;border-left:4px solid #4CAF50;box-shadow:0 2px 4px rgba(0,0,0,0.05)}
        .niuniu_job-info-row{display:flex;align-items:left;gap:10px;flex-wrap:wrap}
        .niuniu_company-tags-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:24px;padding:4px 0;border-top:1px solid rgba(76,175,80,0.1);margin-top:4px}
        .niuniu_company-tags-container{display:flex;align-items:center;margin-left:12px;gap:8px;flex-wrap:wrap;width:100%}
        .niuniu_company-tag-label{font-size:12px;color:#666;font-weight:500;margin-right:8px;white-space:nowrap}
        .niuniu_date-tag{display:inline-flex;align-items:center;padding:4px 8px;border-radius:12px;color:white;font-size:12px;font-weight:500;white-space:nowrap}
        .niuniu_info-tag{display:inline-flex;align-items:center;color:#6c757d;font-size:12px;white-space:nowrap}
        .niuniu_link{display:inline-flex;align-items:center;color:#007bff;text-decoration:none;font-size:12px;cursor:pointer;transition:color 0.2s ease}
        .niuniu_link:hover{color:#0056b3;text-decoration:none}
        @media (max-width:768px){.niuniu_job-info-layer{gap:6px;padding:10px 12px}.niuniu_job-info-row{gap:8px}.niuniu_company-tags-row{gap:6px;padding:3px 0}.niuniu_company-tags-container{gap:6px}.niuniu_company-tag-label{font-size:11px;margin-right:6px}.niuniu_company-query-btn{font-size:11px;padding:4px 8px;min-width:50px}.niuniu_company-tag{font-size:10px;padding:2px 6px}.niuniu_info-tag{font-size:11px}.niuniu_date-tag{font-size:11px;padding:3px 6px}}
        
        .niuniu_feature-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:none;justify-content:center;align-items:center;z-index:10001;backdrop-filter:blur(3px)}
        .niuniu_feature-modal.show{display:flex}
        .niuniu_feature-content{background:linear-gradient(135deg,#ffffff 0%,#f8f9fa 100%);border-radius:16px;padding:30px;max-width:600px;max-height:80vh;overflow-y:auto;box-shadow:0 20px 40px rgba(0,0,0,0.2);transform:scale(0.8);transition:all 0.3s cubic-bezier(0.4,0,0.2,1);position:relative;border:1px solid rgba(76,175,80,0.2)}
        .niuniu_feature-modal.show .niuniu_feature-content{transform:scale(1)}
        .niuniu_feature-header{text-align:center;margin-bottom:25px;padding-bottom:20px;border-bottom:2px solid #e9ecef}
        .niuniu_feature-title{font-size:24px;font-weight:bold;color:#2c3e50;margin-bottom:8px;background:linear-gradient(135deg,#4CAF50 0%,#45a049 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
        .niuniu_feature-subtitle{font-size:14px;color:#6c757d;margin:0}
        .niuniu_feature-section{margin-bottom:25px}
        .niuniu_feature-section h3{font-size:18px;color:#2c3e50;margin-bottom:15px;display:flex;align-items:center;gap:8px}
        .niuniu_feature-list{list-style:none;padding:0;margin:0}
        .niuniu_feature-list li{padding:8px 0;color:#495057;font-size:14px;line-height:1.6;display:flex;align-items:flex-start;gap:10px}
        .niuniu_feature-close{position:absolute;top:15px;right:20px;background:none;border:none;font-size:24px;color:#6c757d;cursor:pointer;padding:5px;border-radius:50%;transition:all 0.3s ease;width:35px;height:35px;display:flex;align-items:center;justify-content:center}
        .niuniu_feature-close:hover{background:#f8f9fa;color:#e74c3c;transform:rotate(90deg)}
        .niuniu_feature-highlight{background:linear-gradient(135deg,#fff3cd 0%,#ffeaa7 100%);border-left:4px solid #ffc107;padding:15px;border-radius:8px;margin-top:15px}
        @media (max-width:768px){.niuniu_feature-content{margin:20px;padding:20px;max-width:calc(100% - 40px)}.niuniu_feature-title{font-size:20px}.niuniu_feature-section h3{font-size:16px}}
    `);



    
    function setupGlobalErrorHandling() {
        window.addEventListener('error', function(event) {
            if (event.error && event.error.message && 
                event.error.message.includes('牛牛查')) {
                logger.error('全局错误捕获:', {
                    message: event.error.message,
                    filename: event.filename,
                    lineno: event.lineno,
                    colno: event.colno,
                    stack: event.error.stack
                });
            }
        });
        window.addEventListener('unhandledrejection', function(event) {
            if (event.reason && event.reason.message && 
                event.reason.message.includes('牛牛查')) {
                logger.error('未处理的Promise拒绝:', event.reason);
            }
        });
        logger.log('全局错误处理器已设置');
    }
  
    
        


    

    function checkCompanyBlacklist(companyName) {
        if (!companyName) return [];
        
        const matches = [];
        const normalizedCompanyName = companyName.toLowerCase().replace(/\s+/g, '');
        
        // 遍历所有黑名单类型
        Object.keys(COMPANY_BLACKLISTS).forEach(function(listType) {
            const blacklist = COMPANY_BLACKLISTS[listType];
            
            // 模糊匹配公司名称,记录匹配到的关键词
            const matchedKeywords = [];
            blacklist.companies.forEach(function(blacklistedCompany) {
                const normalizedBlacklisted = blacklistedCompany.toLowerCase().replace(/\s+/g, '');
                
                // 双向模糊匹配
                if (normalizedCompanyName.includes(normalizedBlacklisted) || 
                    normalizedBlacklisted.includes(normalizedCompanyName)) {
                    matchedKeywords.push(blacklistedCompany);
                }
            });
            
            if (matchedKeywords.length > 0) {
                matches.push({
                    type: listType,
                    name: blacklist.name,
                    emoji: blacklist.emoji,
                    color: blacklist.color,
                    matchedKeywords: matchedKeywords,
                    companyName: companyName
                });
                logger.log('牛牛查求职助手: 发现匹配公司', companyName, '->', blacklist.name);
            }
        });
        return matches;
    }
    

    function createCompanyTag(matchInfo) {
        const tag = document.createElement('span');
        tag.className = `niuniu_company-tag ${matchInfo.type}`;
        
        // 显示匹配到的关键词(最多显示2个)
        const displayKeywords = matchInfo.matchedKeywords.slice(0, 2).join('、');
        const hasMoreKeywords = matchInfo.matchedKeywords.length > 2;
        const keywordText = hasMoreKeywords ? `${displayKeywords}等` : displayKeywords;
        
        tag.textContent = `${matchInfo.emoji} ${matchInfo.name} `;
        
        // 在title中显示所有匹配关键词
        const allKeywords = matchInfo.matchedKeywords.join('、');
        tag.title = `该公司被标记为:${matchInfo.name}类型\n匹配关键词:${allKeywords}\n公司名称:${matchInfo.companyName}`;
        
        tag.style.backgroundColor = matchInfo.color;
        
        // 添加点击事件显示详情
        tag.addEventListener('click', function(e) {
            e.stopPropagation();
            showCompanyTagDetails(matchInfo);
        });
        
        return tag;
    }
    

    function showCompanyTagDetails(matchInfo) {
        const blacklist = COMPANY_BLACKLISTS[matchInfo.type];
        const allKeywords = matchInfo.matchedKeywords.join('、');
        const message = `
公司名称:${matchInfo.companyName}
公司类型:${matchInfo.name}
匹配关键词:${allKeywords}
标记原因:该公司名称匹配了${matchInfo.name}类型名单中的关键词
建议:请谨慎考虑该职位,建议详细了解公司情况后再做决定

匹配规则:模糊匹配公司名称关键词
名单来源:基于公开信息和用户反馈整理

注意:此标记仅供参考,具体情况请自行判断
        `;
        
        alert(message);
    }
    

    function addCompanyTags(companyName) {
        const matches = checkCompanyBlacklist(companyName);
        
        if (matches.length === 0) {
            return null;
        }
        
        const tagContainer = document.createElement('div');
        tagContainer.className = 'niuniu_company-tags-container';
        
        // 添加标签标题
        const tagLabel = document.createElement('span');
        tagLabel.className = 'niuniu_company-tag-label';
        tagLabel.textContent = '🏷️ 公司标签:';
        tagContainer.appendChild(tagLabel);
        
        matches.forEach(function(match) {
            const tag = createCompanyTag(match);
            tagContainer.appendChild(tag);
        });
        
        return tagContainer;
    }
    


    

    function interceptAPIRequests() {
        try {
            logger.log('开始初始化API拦截器...');
            
            // 拦截Fetch API
            const originalFetch = window.fetch;
            if (originalFetch && typeof originalFetch === 'function') {
                // 检查是否已经被牛牛查脚本拦截
                if (originalFetch._niuniuIntercepted) {
                    logger.log('Fetch API已被牛牛查脚本拦截,跳过重复设置');
                } else {
                    window.fetch = function(...args) {
                        const url = args[0] || '';
                        return originalFetch.apply(this, args).then(response => {
                            if (isTargetApi(url)) {
                                logger.log('✅ 拦截到目标API (Fetch):', url);
                                handleApiResponse(response.clone(), url);
                            }
                            return response;
                        }).catch(err => {
                            logger.error('Fetch请求失败:', err);
                            throw err;
                        });
                    };
                    
                    // 标记已拦截
                    window.fetch._niuniuIntercepted = true;
                    
                    logger.log('Fetch API拦截器已设置(增强兼容模式)');
                }
            } else {
                logger.warn('Fetch API不可用或不是函数');
            }

            // 拦截XMLHttpRequest
            interceptXMLHttpRequest();

            logger.log('API拦截器初始化完成');
        } catch (error) {
            logger.error('初始化API拦截器失败:', error);
        }
    }
    

    function interceptXMLHttpRequest() {
        if (XMLHttpRequest.prototype._niuniuIntercepted) {
            logger.log('XMLHttpRequest已被牛牛查脚本拦截,跳过重复设置');
            return;
        }
        
        const originalXHROpen = XMLHttpRequest.prototype.open;
        const originalXHRSend = XMLHttpRequest.prototype.send;
        
        if (typeof originalXHROpen !== 'function' || typeof originalXHRSend !== 'function') {
            logger.warn('XMLHttpRequest原始方法不可用,跳过拦截');
            return;
        }

        XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
            this._niuniuUrl = url;
            this._niuniuMethod = method;
            const args = [method, url];
            if (arguments.length > 2 && async !== undefined) args.push(async);
            if (arguments.length > 3 && user !== undefined) args.push(user);
            if (arguments.length > 4 && password !== undefined) args.push(password);
            return originalXHROpen.apply(this, args);
        };

        XMLHttpRequest.prototype.send = function(data) {
            const self = this;
            const oldOnReadyStateChange = this.onreadystatechange;

            this.onreadystatechange = function() {
                if (self.readyState === 4 && self.status === 200) {
                    const url = self._niuniuUrl || '';
                    if (isTargetApi(url)) {
                        logger.log('✅ 拦截到目标API (XHR):', url);
                        try {
                            const responseData = JSON.parse(self.responseText);
                            const processedData = processJobData(url, responseData);
                            apiData = processedData;
                            logger.log('处理后的API数据:', apiData);
                        } catch (parseError) {
                            logger.error('解析API响应失败:', parseError);
                        }
                    }
                }
                
                if (typeof oldOnReadyStateChange === 'function') {
                    oldOnReadyStateChange.apply(this, arguments);
                }
            };

            return originalXHRSend.apply(this, arguments);
        };

        XMLHttpRequest.prototype._niuniuIntercepted = true;
        logger.log('XMLHttpRequest拦截器已设置');
    }
        
    

    async function handleApiResponse(response, url) {
        if (isTargetApi(url)) {
            const data = await response.json();
            const processedData = processJobData(url, data);
            apiData = processedData;
            logger.log('API数据处理完成,数据长度:', Array.isArray(apiData) ? apiData.length : 'not array');
        }
    }
    

    function isTargetApi(url) {
        if (!config.apiPatterns || !Array.isArray(config.apiPatterns)) {
            return false;
        }
        
        return config.apiPatterns.some(pattern => {
            const isMatch = url.includes(pattern);
            if (isMatch) {
                logger.log('API匹配成功:', pattern, '在URL:', url);
            }
            return isMatch;
        });
    }


    

    function processJobData(url,data) {
        try {
            if (currentHost === 'we.51job.com') {
                return process51JobData(url,data);
            } else if (currentHost === 'www.zhipin.com') {
                try {
                    let urlObj = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin);
                    const pageParam = urlObj.searchParams.get('page');
                    const page = pageParam ? parseInt(pageParam) : 1;
                    
                    if (page === 1) {
                        logger.log('检测到第一页数据,清空历史数据');
                        apiData = [];
                    }
                } catch (urlError) {
                    logger.error('URL解析失败:', urlError);
                }
                               
                return processBossData(url,data);
            }
            logger.warn('未知的网站类型:', currentHost);
            return [];
        } catch (error) {
            logger.error('处理职位数据失败:', error);
            return [];
        }
    }
    

    function process51JobData(url,data) {
        if (data && data.status === "1" && data.resultbody?.job?.items) {
            logger.log('成功解析到', data.resultbody.job.items.length, '条职位数据');
            return data.resultbody.job.items;
        }
        logger.warn('51job API数据格式不正确:', data);
        return [];
    }
    

    function processBossData(url,data) { 
        if (data && data.message === "Success" && data.zpData.jobList) {
            logger.log('成功解析到', data.zpData.jobList.length, '条职位数据');
            let joblist = data.zpData.jobList;
            
            if (apiData && apiData.length > 0) {
                let currentMaxIndex = Math.max(...apiData.map(item => item.index || 0));
                joblist.forEach((job, idx) => {
                    job.index = currentMaxIndex + idx + 1;
                });
                apiData.push(...joblist);
                return apiData;
            } else {
                return joblist;
            }
        }
        logger.warn('BOSS直聘 API数据格式不正确:', data);
        return [];
    }


    

    function createCompanyQueryLayer(companyName) {
        const layer = document.createElement('div');
        layer.className = 'niuniu_company-query-layer';

        // 创建主按钮
        const mainBtn = createMainButton(companyName);
        layer.appendChild(mainBtn);

        // 创建弹出层
        const popup = createPopupMenu(companyName);
        layer.appendChild(popup);

        // 绑定事件
        bindPopupEvents(layer, mainBtn, popup, companyName);

        return layer;
    }
    

    function createMainButton(companyName) {
        const btn = document.createElement('span');
        btn.className = 'niuniu_company-query-btn';
        btn.textContent = '🔍 牛牛查公司';
        btn.title = '查询公司信息: ' + companyName;
        return btn;
    }
    

    function createPopupMenu(companyName) {
        const popup = document.createElement('div');
        popup.className = 'niuniu_query-popup';

        QUERY_SERVICES.forEach(service => {
            const link = document.createElement('a');
            link.href = service.url + encodeURIComponent(companyName);
            link.target = '_blank';
            link.textContent = service.name;
            link.addEventListener('click', (e) => {
                e.stopPropagation();
                window.open(link.href, '_blank');
                hidePopup(popup);
            });
            popup.appendChild(link);
        });

        return popup;
    }
    

    function bindPopupEvents(layer, btn, popup, companyName) {
        let hideTimeout;
        
        // 主按钮点击事件
        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            window.open(QUERY_SERVICES[0].url + encodeURIComponent(companyName), '_blank');
        });
        
        // 鼠标悬停显示弹出层
        btn.addEventListener('mouseenter', () => {
            clearTimeout(hideTimeout);
            showPopup(popup);
        });
        
        // 鼠标离开延迟隐藏
        layer.addEventListener('mouseleave', () => {
            hideTimeout = setTimeout(() => hidePopup(popup), 300);
        });
        
        // 鼠标进入取消隐藏
        layer.addEventListener('mouseenter', () => {
            clearTimeout(hideTimeout);
        });
    }
    

    function showPopup(popup) {
        popup.classList.add('show');
    }
    

    function hidePopup(popup) {
        popup.classList.remove('show');
    }

    function showFeatureIntroduction() {
        // 检查是否已存在弹出层
        let modal = document.getElementById('niuniu_feature_modal');
        if (modal) {
            modal.classList.add('show');
            return;
        }
        
        // 创建弹出层
        modal = document.createElement('div');
        modal.id = 'niuniu_feature_modal';
        modal.className = 'niuniu_feature-modal';
        
        modal.innerHTML = `
            <div class="niuniu_feature-content">
                <button class="niuniu_feature-close" onclick="this.closest('.niuniu_feature-modal').classList.remove('show')">×</button>
                
                <div class="niuniu_feature-header">
                    <h2 class="niuniu_feature-title">🏷️ 牛牛查求职助手</h2>
                    <p class="niuniu_feature-subtitle">智能招聘信息增强工具 - 让求职更安全、更高效</p>
                </div>
                
                <div class="niuniu_feature-section">
                    <h3>🎆 核心功能</h3>
                    <ul class="niuniu_feature-list">
                        <li>🏷️ <strong>公司标签系统</strong> - 智能识别诈骗、外包、培训公司,帮您身引风险</li>
                        <li>🔍 <strong>公司信息查询</strong> - 查询公司工商信息,支持天眼查、爱企查等平台</li>
                        <li>📅 <strong>职位时间追踪</strong> - 显示职位首发和更新时间,识别新鲜职位</li>
                        <li>💼 <strong>职位详情增强</strong> - 自动显示学历、经验要求,信息一目了然</li>
                        <li>🔗 <strong>链接智能解码</strong> - 自动解码加密链接,方便查看真实链接地址</li>
                    </ul>
                </div>
                
                <div class="niuniu_feature-section">
                    <h3>🏠 公司标签系统</h3>
                    <ul class="niuniu_feature-list">
                        <li>⚠️ <strong style="color: #FF5722;">诈骗公司</strong> - 红色警告标签,提醒您谨慎对待</li>
                        <li>🔄 <strong style="color: #FF9800;">外包公司</strong> - 橙色提醒标签,了解工作性质</li>
                        <li>📚 <strong style="color: #2196F3;">培训公司</strong> - 蓝色信息标签,识别培训机构</li>
                        <li>🚨 <strong style="color: #9C27B0;">自定义标签</strong> - 紫色标记,支持个性化配置</li>
                    </ul>
                </div>
                
                <div class="niuniu_feature-section">
                    <h3>🚀 特色亮点</h3>
                    <ul class="niuniu_feature-list">
                        <li>🧪 <strong>智能匹配</strong> - 采用模糊匹配算法,精准识别公司类型</li>
                        <li>🌍 <strong>多平台支持</strong> - 兼容 <a href="https://www.51job.com">51job</a>、<a href="https://www.zhipin.com">BOSS直聘</a>、<a href="https://www.niuqizp.com">牛企直聘</a>等招聘平台</li>
                        <li>📱 <strong>响应式设计</strong> - 适配桌面和移动设备,体验一致</li>
                        <li>⚡ <strong>实时更新</strong> - 自动监听页面变化,实时显示信息</li>
                        <li>🔒 <strong>隐私保护</strong> - 本地处理,不收集个人信息</li>
                    </ul>
                </div>
                
                <div class="niuniu_feature-highlight">
                    <strong>💡 使用提示:</strong>
                    点击公司标签可查看详细信息,点击查询按钮可快速跳转到公司信息查询平台。
                    本工具仅供参考,建议结合多种渠道获取公司信息。
                </div>
            </div>
        `;
        
        // 添加到页面
        document.body.appendChild(modal);
        
        // 显示弹出层
        setTimeout(() => {
            modal.classList.add('show');
        }, 100);
        
        // 点击背景关闭
        modal.addEventListener('click', function(e) {
            if (e.target === modal) {
                modal.classList.remove('show');
            }
        });
        
        // ESC键关闭
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape' && modal.classList.contains('show')) {
                modal.classList.remove('show');
            }
        });
        
        logger.log('功能介绍弹出层已显示');
    }

    function initTipButton() {
        if (!initConfig.showTipButton) {
            return;
        }
        
        const tipBtn = document.createElement('div');
        tipBtn.innerHTML = '🏷️';
        tipBtn.title = '牛牛查求职助手 ';
        tipBtn.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 50px;
            height: 50px;
            background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
            color: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 20px;
            box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
            z-index: 9998;
            transition: all 0.3s ease;
        `;
        
        tipBtn.addEventListener('mouseenter', function() {
            this.style.transform = 'scale(1.1)';
            this.style.boxShadow = '0 6px 16px rgba(76, 175, 80, 0.4)';
        });
        
        tipBtn.addEventListener('mouseleave', function() {
            this.style.transform = 'scale(1)';
            this.style.boxShadow = '0 4px 12px rgba(76, 175, 80, 0.3)';
        });
        
        tipBtn.addEventListener('click', function() {
            showFeatureIntroduction();
        });
        
        document.body.appendChild(tipBtn);
        logger.log('功能提示按钮已加载');
    }

    

    function calculateDaysDifference(date) {
        try {
            let inputDate = parseDate(date);
            
            if (!inputDate || isNaN(inputDate.getTime())) {
                logger.warn('无效的日期输入:', date);
                return 0;
            }
            
            const today = new Date();
            // 标准化时间到当天凌晨
            inputDate.setHours(0, 0, 0, 0);
            today.setHours(0, 0, 0, 0);
            
            const diffTime = today - inputDate;
            const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
            
            return diffDays < 0 ? 0 : diffDays; // 处理未来日期情况
        } catch (error) {
            logger.error('计算日期差值失败:', error);
            return 0;
        }
    }
    

    function parseDate(date) {
        if (date instanceof Date) {
            return new Date(date);
        }
        
        if (typeof date === 'number') {
            return new Date(date);
        }
        
        if (typeof date === 'string') {
            return parseStringDate(date.trim());
        }
        
        return null;
    }
    

    function parseStringDate(dateStr) {
        if (!dateStr) return null;
        
        // 尝试直接解析
        let inputDate = new Date(dateStr);
        if (!isNaN(inputDate.getTime())) {
            return inputDate;
        }
        
        // 尝试提取日期部分
        const datePart = dateStr.split(/[ ,TZ]/)[0].replace(/[^\d-]/g, '');
        inputDate = new Date(datePart);
        
        if (!isNaN(inputDate.getTime())) {
            return inputDate;
        }
        
        // 尝试标准化分隔符
        const normalizedDate = datePart.replace(/[\/./]/g, '-');
        inputDate = new Date(normalizedDate);
        
        if (!isNaN(inputDate.getTime())) {
            return inputDate;
        }
        
        // 尝试重建日期格式
        return reconstructDate(datePart);
    }
    

    function reconstructDate(datePart) {
        const digits = datePart.match(/\d+/g);
        if (!digits || digits.length < 3) {
            return null;
        }
        
        let year, month, day;
        
        if (digits[0].length === 4) {
            [year, month, day] = digits;
        } else if (digits[2].length === 4) {
            [month, day, year] = digits;
        } else {
            [year, month, day] = digits;
        }
        
        const reconstructedDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
        let inputDate = new Date(reconstructedDate);
        
        // 如果日期无效,尝试交换月和日
        if (isNaN(inputDate.getTime())) {
            const swappedDate = `${year}-${day.padStart(2, '0')}-${month.padStart(2, '0')}`;
            inputDate = new Date(swappedDate);
        }
        
        return isNaN(inputDate.getTime()) ? null : inputDate;
    }


    function getDateStyle(days) {
        if (days <= 7) return DATE_COLORS.fresh;
        if (days <= 14) return DATE_COLORS.recent;
        if (days <= 60) return DATE_COLORS.normal;
        if (days <= 90) return DATE_COLORS.old;
        return DATE_COLORS.expired;
    }

    function processDetailPage(){
        if ( currentHost === 'campus.niuqizp.com'){
            processNiuqizpDetailPage();
        }
    }

    function processNiuqizpDetailPage(){
        const detailContainer = document.querySelector(config.detailContentSelector);
        if (!detailContainer) {
            logger.warn('未找到详情页容器元素');
            return;
        }
        const jobmetaContainer = document.querySelector(config.detailMoreLayer);

        let companyNameStr = detailContainer.querySelector(config.detailCompanyName).textContent.trim();
        // 去除年份(2025、2026等)或招聘之后的字符
        companyNameStr = companyNameStr.replace(/(202[0-9]|招聘).*$/, '').trim();
        logger.log(`元素 ${companyNameStr}`);

        let jobData={
                fullCompanyName: companyNameStr,
                jobTitle: null,
                degreeString: null,
                workYearString: null,
                confirmDateString: null,
                updateDateTime: null,
                jobHref: null,
                jobDescribe: null    
        }   
        const infoLayer = createJobInfoLayer(jobData);
        insertInfoLayer(jobmetaContainer, infoLayer);
        
        
        let hrefItems = detailContainer.querySelectorAll(config.detailOutLink);
        logger.log(`链接元素 ${hrefItems.length}`);
        hrefItems.forEach((hrefItem,index) => { 
            try {
                var base64Encoded = hrefItem.getAttribute('ref');
                // 进行Base64解码
                var decodedUrl = atob(base64Encoded);
                
                // 移除原有的事件监听器
                const newHrefItem = hrefItem.cloneNode(true);
                hrefItem.parentNode.replaceChild(newHrefItem, hrefItem);
                
                // 替换文本内容和href
                newHrefItem.textContent = decodedUrl;
                newHrefItem.style = 'border-left:4px solid #4CAF50;border-radius:6px;padding:4px;margin-left:4px;';
                newHrefItem.href = decodedUrl;
                
                // 添加新的点击事件(直接打开链接)
                newHrefItem.addEventListener('click', function(e) {
                    e.preventDefault();
                    window.open(decodedUrl, '_blank');
                });
                
            } catch (error) {
                logger.error(`链接元素${index} Base64解码失败: ${error}`);
            }         
        })
    }
    

    function processCompanyList() {
        const jobItems = document.querySelectorAll(config.jobListSelector);
        logger.log('DOM找到', jobItems.length, '个职位项目');
 
        jobItems.forEach((jobElement, index) => {
            try {
                if (shouldSkipElement(jobElement, index)) return;      
                const jobData = getJobData(index, jobElement);
                if (!jobData) {
                    logger.warn(`第${index}个职位没有数据`);
                    return;
                }              
                logger.log(`处理职位 ${index}: ${jobData.fullCompanyName}`);
                
                const infoLayer = createJobInfoLayer(jobData);
                insertInfoLayer(jobElement, infoLayer);
                
                markElementAsProcessed(jobElement);
                logger.log(`元素 ${index} 处理完成`);
            } catch (error) {
                logger.error(`处理元素 ${index} 时出错:`, error);
            }
        });
    }
    

    function shouldSkipElement(element, index) {
        if (element.dataset.processed) {
            logger.log(`元素 ${index} 已处理过,跳过`);
            return true;
        }
        return false;
    }

    function process51JobListData(data){
            return {
                fullCompanyName: data['fullCompanyName'],
                jobTitle: data['jobName'],
                degreeString: data['degreeString'],
                workYearString: data['workYearString'],
                confirmDateString: data['confirmDateString'],
                updateDateTime: data['updateDateTime'],
                jobHref: data['jobHref'],
                jobDescribe: data['jobDescribe'],
            };
    }

    function processBossListData(data){
            return {
                fullCompanyName: data['brandName'],
                jobTitle: data['jobName'],
                degreeString: data['jobDegree'],
                workYearString: data['jobExperience'],
                confirmDateString: '',
                updateDateTime: '',
                jobHref: '',
                jobDescribe:'',
            };
    }   

    function getJobData(index, jobElement) {
        // 优先使用API数据
        
        if (apiData && apiData[index]) {
            if (currentHost === 'we.51job.com') {
                return process51JobListData(apiData[index]);
            } else if (currentHost === 'www.zhipin.com') {
                return processBossListData(apiData[index]);
            }              
        }
      
        // 如果API数据不可用,从 DOM 中提取基本信息
        logger.log(`API数据不可用,从 DOM 提取职位 ${index} 的信息`);
        
        try {
            const companyElement = jobElement.querySelector(config.companyNameSelector);
            const jobTitleElement = jobElement.querySelector(config.jobTitleSelector);
            
            const companyName = companyElement ? companyElement.textContent.trim() : `公司${index + 1}`;
            const jobTitle = jobTitleElement ? jobTitleElement.textContent.trim() : `职位${index + 1}`;
            
            // 创建基本数据结构
            return {
                fullCompanyName: companyName,
                jobTitle: jobTitle,
                degreeString: '',
                workYearString: '',
                confirmDateString: null,
                updateDateTime: null,
                jobHref: null,
                jobDescribe: `${companyName} - ${jobTitle}`
            };
        } catch (error) {
            logger.error(`提取职位 ${index} DOM 信息失败:`, error);
            return null;
        }
    }
    

    function createJobInfoLayer(jobData) {
        const infoLayer = document.createElement('div');
        infoLayer.className = 'niuniu_job-info-layer';
        
        // 第一行:公司查询按钮、学历经验信息和时间信息
        const mainRow = document.createElement('div');
        mainRow.className = 'niuniu_job-info-row';
        
        // 添加公司查询按钮
        if (jobData.fullCompanyName) {
            const queryLayer = createCompanyQueryLayer(jobData.fullCompanyName);
            mainRow.appendChild(queryLayer);
        }

        // 添加发布时间
        if (jobData.confirmDateString) {
            const daysDiff = calculateDaysDifference(jobData.confirmDateString);
            const createTimeTag = createDateTag('📅 首发', jobData.confirmDateString, daysDiff);
            mainRow.appendChild(createTimeTag);
        }
        
        // 添加更新时间
        if (jobData.updateDateTime) {
            const updaysDiff = calculateDaysDifference(jobData.updateDateTime);
            const updateInfo = createDateTag('🔄 更新', jobData.updateDateTime, updaysDiff);
            mainRow.appendChild(updateInfo);
        }

        // 添加学历和经验信息
        if (jobData.degreeString || jobData.workYearString){
            const degreeInfo = createInfoTag(
                `🎓 学历: ${jobData.degreeString || '不限'} | 💼 经验: ${jobData.workYearString || '不限'}`,
                'niuniu_info-tag'
            );
            mainRow.appendChild(degreeInfo);
        }
        
        // 添加详情链接
        if (jobData.jobHref) {
            const detailLink = createDetailLink(jobData);
            mainRow.appendChild(detailLink);
        }
        
        infoLayer.appendChild(mainRow);
        
        // 第二行:公司标签(独立显示)
        if (jobData.fullCompanyName) {
            const companyTags = addCompanyTags(jobData.fullCompanyName);
            if (companyTags) {
                const tagsRow = document.createElement('div');
                tagsRow.className = 'niuniu_company-tags-row';
                tagsRow.appendChild(companyTags);
                infoLayer.appendChild(tagsRow);
            }
        }
        return infoLayer;
    }
    

    function createInfoTag(text, className) {
        const tag = document.createElement('span');
        tag.className = className;
        tag.textContent = text;
        return tag;
    }
    

    function createDateTag(label, dateString, daysDiff) {
        const tag = document.createElement('span');
        tag.className = 'niuniu_date-tag';
        tag.textContent = `${label}: ${dateString}`;
        tag.title = `${daysDiff}天前`;
        tag.style.background = getDateStyle(daysDiff);
        return tag;
    }
    

    function createDetailLink(jobData) {
        const link = document.createElement('a');
        link.className = 'niuniu_link';
        link.textContent = '📝 详情';
        link.href = jobData.jobHref;
        link.target = '_blank';
        link.title = jobData.jobDescribe || '查看详细信息';
        return link;
    }
    

    function insertInfoLayer(jobElement, infoLayer) {
        jobElement.parentNode.insertBefore(infoLayer, jobElement.nextSibling);
    }
    

    function markElementAsProcessed(element) {
        element.dataset.processed = 'true';
    }
    

    function initProcessWithRetry(retryCount = 0) {
        try {
            // 设置全局错误处理
            setupGlobalErrorHandling();
            
            // API拦截器已在脚本开始时启动,这里不再重复启动
            // 只是记录一下状态
            console.log('[牛牛查求职助手] initProcessWithRetry - API拦截器已在早期启动');

            setTimeout(() => {
                const testElement = document.querySelector(config.companyNameSelector);
                if (testElement || retryCount >= initConfig.maxInitialRetries) {
                    processPage();
                    logger.log(`初始化处理完成,重试次数: ${retryCount}`);
                } else {
                    logger.log(`未检测到目标元素,将在${initConfig.retryInterval}ms后重试(${retryCount+1}/${initConfig.maxInitialRetries})`);
                    initProcessWithRetry(retryCount + 1);
                }
            }, initConfig.initialLoadDelay);
        } catch (error) {
            logger.error('初始化处理失败:', error);
        }
    }


    function processPage() {
        try {
            logger.log('开始处理页面...');
            
            if (currentHost === 'campus.niuqizp.com'){
                processDetailPage();
            }else{
                processCompanyList();
                // 设置MutationObserver监听动态加载的内容
                setupMutationObserver();                
            }
            
        } catch (error) {
            logger.error('处理页面时发生错误:', error);
        }
    }
         

    function isScriptOwnChange(mutation) {
        if (mutation.addedNodes && mutation.addedNodes.length > 0) {
            for (let node of mutation.addedNodes) {
                if (node.nodeType === 1) {
                    if (isScriptElement(node) || 
                        (node.querySelector && node.querySelector('[class*="niuniu_"]'))) {
                        return true;
                    }
                }
            }
        }
        
        if (mutation.target && mutation.target.nodeType === 1) {
            if (isScriptElement(mutation.target) || 
                mutation.target.closest('[class*="niuniu_"]')) {
                return true;
            }
        }
        
        return false;
    }
    

    function isScriptElement(element) {
        if (!element || !element.className) return false;
        
        // 检查类名是否包含脚本标识
        const className = element.className;
        if (typeof className === 'string') {
            return className.includes('niuniu_');
        }
        
        return false;
    }


    function setupMutationObserver() {
        let targetNode = document.querySelector(config.waitForElement);
        logger.log('等待监听的目标元素:', config.waitForElement, targetNode);
        
        if (!targetNode) {
            logger.warn('未找到目标监听元素:', config.waitForElement);
            targetNode = document.body;
        }
        
        if (!targetNode || !targetNode.nodeType || targetNode.nodeType !== Node.ELEMENT_NODE) {
            logger.error('无法找到有效的DOM节点进行监听,延迟重试...');
            setTimeout(() => {
                setupMutationObserver();
            }, 2000);
            return;
        }
        
        let isProcessing = false;
        
        const observer = new MutationObserver((mutations) => {
            if (isProcessing) return;
            
            let hasNewJobs = false;
            
            mutations.forEach(mutation => {
                if (isScriptOwnChange(mutation)) {
                    return;
                }
                
                if (mutation.type === 'childList') {
                    if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === 1 && 
                                (node.matches && node.matches(config.jobListSelector) || 
                                 node.querySelector && node.querySelector(config.jobListSelector))) {
                                hasNewJobs = true;
                            }
                        });
                    }
                }
            });
            
            if (hasNewJobs) {
                logger.log('检测到新职位,开始处理');
                
                isProcessing = true;
                setTimeout(() => {
                    processCompanyList();
                    isProcessing = false;
                }, 1200);
            }
        });
        
        observer.observe(targetNode, {
            childList: true,
            subtree: true
        });
        
        logger.log('已启动MutationObserver监听动态内容');
    }

    $(document).ready(function() {
        logger.log('jQuery已加载,准备启动脚本...');

        setTimeout(() => {
            initProcessWithRetry();
            
            setTimeout(function() {
                initTipButton();
            }, 3000);
        }, 1000);
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址