Loading

油猴脚本记录

在浏览器中使用 脚本,需要使用油猴脚本谷歌油猴脚本链接 | FireFox火狐 油猴子链接

脚本编写技巧

元素定位


即使是使用ai辅助,也很难准确进行元素的定位,这需要我们在实际的页面中通过 元素选取 来获得元素路径。
例如想要点击下面的这个按钮

image
我们可以通过右键点击这个元素,选择 复制 -> CSS选择器

然后通过 document.querySelector('【复制的内容】')来进行查找

元素选取是指 image

注意:需要确认当前的document确实包含该元素 元素可能是在页面的其他frame中

通过控制台确定元素所在 frame


通过 元素选取 选定元素后,通过控制器的右下角查看该元素所在的frame

在 查看器 中复制该frame对应数据

然后通过如下代码切换当前的 document

const frame = document.querySelector('iframe[src*="【复制的src】"]');
if(!frame) return console.error('找不到对应frame');
const doc = frame1.contentDocument || frame.contentWindow.document;

完整脚本记录


1. 培训课程信息录入

// ==UserScript==
// @name         批量导入培训课程信息
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  读取本地 Excel 文件,自动打开录入弹窗并依次填写提交
// @match        http://px.dg/*
// @grant        none
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @run-at       document-end
// @noframes
// ==/UserScript==

(function() {
  'use strict';

      // 注入文件选择面板
      const panel = document.createElement('div');
      let count = 0; // 记录导入条数
      panel.style.cssText = 'position:fixed;top:10px;right:10px;padding:5px;background:#fff;border:1px solid #ccc;z-index:9999';
      panel.innerHTML = `
      <input type="file" id="tm_import_file" accept=".xlsx,.xls">
      <button id="tm_start_import">开始导入</button>
      `;
      document.body.appendChild(panel);

      document.getElementById('tm_start_import').addEventListener('click', () => {
      const fileInput = document.getElementById('tm_import_file');
      const file = fileInput.files[0];
      if (!file) return alert('请选择 Excel 文件(.xls/.xlsx)');

      // 格式校验:只允许 .xls 或 .xlsx
      const name = file.name || '';
      const ext = name.split('.').pop().toLowerCase();
      if (!['xls', 'xlsx'].includes(ext)) {
        fileInput.value = '';
        return alert('文件格式错误,请选择 .xls 或 .xlsx 格式的 Excel 文件!');
      }
      const reader = new FileReader();
      reader.readAsBinaryString(file);
      reader.onload = evt => {
      const data = evt.target.result;
      const wb = XLSX.read(data, { type: 'binary' });
      const sheet = wb.Sheets[wb.SheetNames[0]];
      const rows = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '' });
      if (rows.length <= 1) return alert('Excel 没有数据!');

      // 关键列校验
      const headers = rows[1].map(String);
      const required = ['学习培训名称', '学时'];
      const missing = required.filter(h => !headers.includes(h));
      if (missing.length) {
         return alert('缺少必须的列:' + missing.join(',') + ',请确认选择了正确的文件');
	}
      processRows(rows, 2);  // 跳过表头,从第 2 行开始
    };
  });

  // 递归逐行处理
  function processRows(rows, idx) {
    if (idx >= rows.length) {
      return alert('全部导入完成!\n一共导入了'+ count +'条课程');
    }
    const cols = rows[idx];
    if (!cols || cols.length < 11) {
      return processRows(rows, idx + 1);
    }

    // 从列数组中取值
    const name    = String(cols[1] || '').trim();   // 学习培训名称
    const rawTime = cols[3];   // 时间
    const rawCat  = String(cols[6] || '').trim();   // 培训类别原始
    const hours   = String(cols[8] || '').trim();   // 学时
    const number  = String(cols[10]|| '').trim();   // 培训人数
    
    console.log("格式:"+typeof rawTime+' '+rawTime);
    
    // 如果没有时间相关信息,说明已到数据尾行,结束导入
    if(typeof rawTime === 'undefined' | rawTime == ""){
      return alert('全部导入完成!\n一共导入了 '+ count +' 条课程');
    }

    // 解析时间
    let begin, end;
    const year = new Date().getFullYear();
    
    if (typeof rawTime === 'number'){
      const d = XLSX.SSF.parse_date_code(rawTime);
      begin = `${d.y}-${pad(d.m)}-${pad(d.d)}`;
      end = begin;
    } else{
      const timeStr = String(rawTime || '').trim();
      ({ begin, end } = parseDates(timeStr, year));
    }
    // 类别映射
    const catMap = {
      '政治理论教育': '3',
      '政治教育和政治训练': '3',
      '党章党规党纪教育': '2',
      '党的宗旨教育': '2',
      '革命传统教育': '1',
      '形势政策教育': '4',
      '知识技能教育': '6'
    };
    const catValue = catMap[rawCat] || '';
    
//     document.querySelectorAll('iframe').forEach(f=>console.log(f.src));
    
    const frame = document.querySelector('iframe[src*="dxNewclassinfoController.do?list&areaType=A&clickFunctionId=402881dd65120d970165123005410004"]');
    if(!frame) return alert("错误:未处于【自主培训】页面\n找不到对应frame");
    const doc = frame.contentDocument || frame.contentWindow.document;

    // 点击“录入”按钮
    doc.querySelector('div.datagrid-toolbar:nth-child(3) > span:nth-child(1) > a:nth-child(1) > span:nth-child(1)').click();

    // 等待弹窗加载
    const timer = setInterval(() => {
      const frame1 = document.querySelector('iframe[src*="dxNewclassinfoController.do?goAdd&areaType=A"]');
    	if(!frame1) return alert("错误:未打开【录入】页面\n找不到对应frame");
    	const doc1 = frame1.contentDocument || frame.contentWindow.document;
      
      const nameInput = doc1.getElementById('className');
      if (!nameInput) return;
      clearInterval(timer);

      // 填写表单
      nameInput.value = name;
      doc1.getElementById('sciTermBegin').value = begin;
      doc1.getElementById('sciTermEnd').value   = end;
      doc1.getElementById('xueshi').value       = hours;
      doc1.getElementById('sciRecruitNum').value = number;
      doc1.getElementById('pxjg').value         = '黄江镇委党校';

      // 单选(专项培训)
      doc1
        .querySelectorAll('input[name="isAppClass"]')
        .forEach(r => r.value === '2' && (r.checked = true));

      // 下拉(培训类别)
      const sel = doc1.querySelector('select[name="trainType"]');
      if (sel) {
        sel.value = catValue;
        sel.dispatchEvent(new Event('change'));
      }
      
      count++;
      
      setTimeout(() =>{
        window.top.document.querySelector('.ui_state_highlight').click();
      },2000);

      // 固定延时后处理下一行
      setTimeout(() => processRows(rows, idx + 1), 4000);
    }, 2000);
  }

  // 解析日期区间函数,跟之前一样
  function parseDates(str, year) {
    str = str.replace(/\s+/g, '');
    let parts = str.split('至');
    let [bRaw, eRaw] = parts.length > 1 ? parts : [parts[0], parts[0]];
    bRaw = bRaw.replace(/日$/, ''); eRaw = eRaw.replace(/日$/, '');
    const [bM, bD] = bRaw.split('月');
    let eM, eD;
    if (eRaw.includes('月')) [eM, eD] = eRaw.split('月');
    else { eM = bM; eD = eRaw; }
    return {
      begin: `${year}-${pad(bM)}-${pad(bD)}`,
      end:   `${year}-${pad(eM)}-${pad(eD)}`
    };
  }
  
  function pad(v) {
    return String(v).padStart(2, '0');
  }

})();



注意代码中的 match 部分做了隐私处理,复制使用时记得补充

posted @ 2025-07-17 16:11  F丶cat  阅读(87)  评论(0)    收藏  举报