// ==UserScript== // @name 职业技能标准PDF下载 // @namespace http://tampermonkey.net/ // @version 2025.3.6 // @description 职业标准系统下载pdf功能 // @author Shuaima // @match *://www.osta.org.cn/skillStandard* // @grant none // @run-at document-body // @require https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js // @require https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/layer/3.5.1/layer.min.js // @license MIT // ==/UserScript== (function() { 'use strict'; // 创建一个Map对象,用于缓存PDF文件 const pdfCacheMap = new Map(); // 监听页面中带有类名.plugin-tool的元素的点击事件 $(document).on('click', '.plugin-tool', async function() { // 获取点击元素的data-type属性值 const type = $(this).data('type'); // 获取点击元素的data-item属性值 const item = $(this).data('item'); // 如果item为空,弹出提示并返回 if (!item) { return alert('获取数据失败'); } // 从item中获取code、fileName和name const code = item.code; const fileName = item.standardInfo; const name = item.standardInfoName; // 如果pdfCacheMap中已经缓存了该code对应的PDF文件 if (pdfCacheMap.has(code)) { // 获取缓存的数据 const cachedData = pdfCacheMap.get(code); // 增加缓存数据的访问次数 cachedData.times++; // 处理PDF数据 processPdfData(type, name, code, cachedData.data); // 如果访问次数超过2次,从缓存中删除该数据 if (cachedData.times > 2) { pdfCacheMap.delete(code); } return; } // 如果layer库存在,显示加载动画 if (layer) { layer.load(0, { shade: [0.5, '#000'], }); } try { // 发起请求,下载PDF文件 const response = await fetch(`http://www.osta.org.cn/api/sys/downloadFile/decrypt?fileName=${fileName}`); // 将响应转换为Blob对象 const pdfBlob = await response.blob(); // 将Blob对象缓存到pdfCacheMap中 pdfCacheMap.set(code, { data: pdfBlob, times: 1 }); // 处理PDF数据 processPdfData(type, name, code, pdfBlob); } catch (error) { // 捕获并打印错误信息 console.error('Failed to fetch PDF:', error); } finally { // 无论成功或失败,关闭加载动画 if (layer) { layer.closeAll(); } } }); // 处理PDF数据的函数 function processPdfData(type, name, code, pdfData) { // 将PDF数据转换为Blob对象 const blob = new Blob([pdfData], { type: 'application/pdf' }); // 创建Blob对象的URL const url = window.URL.createObjectURL(blob); // 创建一个a标签,用于下载PDF文件 const a = document.createElement('a'); a.download = name; a.href = url; a.click(); // 释放URL对象 window.URL.revokeObjectURL(url); } // 创建一个Map对象,用于缓存请求内容 const requestContentMap = new Map(); // 自定义事件触发器,用于触发ajax相关事件 function ajaxEventTrigger(event) { const ajaxEvent = new CustomEvent(event, { detail: this }); window.dispatchEvent(ajaxEvent); } // 保存原始的XMLHttpRequest对象 const oldXHR = window.XMLHttpRequest; // 创建一个新的XMLHttpRequest对象,并添加事件监听 function newTestXHR() { const realXHR = new oldXHR(); realXHR.addEventListener('readystatechange', function() { ajaxEventTrigger.call(this, 'ajaxReadyStateChange'); }, false); return realXHR; } // 替换全局的XMLHttpRequest对象为新的XMLHttpRequest对象 window.XMLHttpRequest = newTestXHR; // 监听ajaxReadyStateChange事件 window.addEventListener('ajaxReadyStateChange', function (e) { // 获取请求的URL const url = e.detail.responseURL; // 如果URL包含'skillStandardList'并且请求状态为4(完成) if (url && url.includes('skillStandardList') && e.detail.readyState === 4) { // 获取响应文本并解析为JSON对象 const responseText = e.detail.responseText || ''; const data = JSON.parse(responseText) || []; // 延迟500毫秒后调用rebuildTable函数 setTimeout(() => rebuildTable(data), 500); } }); // 重新构建表格的函数 function rebuildTable(data) { // 从data中获取列表数据 const list = (data && data.body && data.body.list) || []; // 将列表数据转换为Map对象,key为code,value为item const listMap = new Map(list.map(item => [item.code, item])); // 移除页面中已有的.plugin-tool元素 $('.plugin-tool').remove(); // 遍历表格中的每一行 $('.arco-table-element tbody > tr').each(function() { // 获取当前行的code const code = $(this).find('td').eq(2).text(); // 从listMap中获取对应的item const item = listMap.get(code); // 如果item不存在,跳过当前行 if (!item) { return; } // 获取当前行的第5列 const toolTd = $(this).find('td').eq(5); // 修改第5列中的a标签文本为“网站查看” toolTd.find('a').text('网站查看'); // 在第5列中添加一个PDF下载的链接 toolTd.find('.arco-table-td-content').append('PDF下载'); // 为新增的PDF下载链接设置data-item属性 toolTd.find('.plugin-tool').data('item', item); }); } })();