采集电商订单
当前为
// ==UserScript== // @name NewGoldenState Userscript // @namespace http://tampermonkey.net/ // @version 2025-09-17 // @description 采集电商订单 // @author You Name // @license MIT // @match https://zhihu.com/* // @match https://juejin.cn/* // @match https://www.baidu.com // @match http://erp-shop.xjzcctv.com/* // @match https://unionwms.jdl.com/* // @connect newerp.xjzcctv.com // @icon https://www.google.com/s2/favicons?sz=64&domain=newerp.xjzcctv.com // @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js // @run-at document-end // @grant GM_log // @grant GM_addStyle // @grant GM_getResourceURL // @grant GM_xmlhttpRequest // @grant GM_getResourceText // ==/UserScript== ; (async function () { 'use strict' if (_) { console.log('[ TemperMonkey ]initial-lodash') } // 登录类型按钮 handleLoginButton() addListener() addInterceptor() initialHttpUtils() })() /************************************************************************************************************************/ // 函数:执行你想在页面上做的操作 function performAction() { // 登录类型按钮 // handleLoginButton() // 查询按钮 // handleQueryButton() // 组波按钮 // handleComposeButton() // 嵌入按钮 // handleInsertButton() // 订单数量按钮 setTimeout(() => { handelOrderGroupButton() }, 3000) } // 登录类型按钮操作 async function handleLoginButton (){ const selector = '#tab-1' try { const button = await waitForElement(selector) const buttonText = button.textContent.trim() console.log('[ TemperMonkey ]找到元素:', button) console.log('[ TemperMonkey ]找到元素:', buttonText) // 记录初始状态 let isActive = button.classList.contains('is-active'); // 创建观察者监控按钮属性变化 const attributeObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { // 监控class属性变化(is-active类的添加/移除) if (mutation.attributeName === 'class') { const newIsActive = button.classList.contains('is-active'); if (newIsActive !== isActive) { isActive = newIsActive; console.log('newIsActive=' + newIsActive) } } }) }) // 观察按钮的属性变化 attributeObserver.observe(button, { attributes: true }) } catch (error) { console.error('抓取元素时出错:', error) } } // 查询按钮操作 async function handleQueryButton() { // 查询按钮构建选择器 - 组合多个特征提高准确性 const queryButtonSelector = 'button.el-button.el-button--primary' + '[type="button"]' + '[clstag="pageclick|keycount|orderAnalysis_btn|search"]' try { // 等待按钮出现 const queryButton = await waitForElementWithText(queryButtonSelector, { text: ' 查询(Q) ' }) const buttonText = queryButton.textContent.trim() console.log('[ TemperMonkey ]找到元素:', buttonText) // 可以在这里添加操作,例如:点击按钮 // queryButton.click(); // setInterval(() => { // queryButton.click() // }, 10000) } catch (error) { console.error('[ TemperMonkey ]处理查询按钮时出错:', error) } } // 组波按钮操作 async function handleComposeButton() { // 组波按钮构建精确的选择器 const composeButtonSelector = 'button.el-button.el-tooltip.el-button--primary.is-disabled' + // 类名组合 '[type="button"]' + // 按钮类型 '[disabled="disabled"]' + // 禁用状态 '[aria-describedby^="el-tooltip-"]' // 匹配tooltip属性(前缀匹配) try { const composeButton = await waitForElementWithText(composeButtonSelector, { text: '组波' }) const buttonText = composeButton.textContent.trim() console.log('[ TemperMonkey ]找到元素:', buttonText) // 监控按钮状态 monitorButtonState(composeButton) } catch (error) { console.error('[ TemperMonkey ]处理组波按钮时出错:', error) } } // 插入按钮操作 async function handleInsertButton() { // 构建精准的选择器 const insertButtonSelector = 'ul.tableModeWrap' try { // 策略1:通过类名直接查找ul元素 const ulElement = await waitForElement(insertButtonSelector, {}) console.log('[ TemperMonkey ]找到元素:', ulElement) createInsertHTML(ulElement) } catch (error) { console.error('抓取元素时出错:', error) } } // 订单数量按钮操作 async function handelOrderGroupButton() { // 构建精准的选择器 const orderGroupButtonSelector = 'td.el-table_1_column_3.el-table__cell' try { const orderGroupButton = await waitForElement(orderGroupButtonSelector) const buttonText = orderGroupButton.textContent.trim() console.log('[ TemperMonkey ]找到元素:', orderGroupButton) console.log('[ TemperMonkey ]找到元素:订单数量', buttonText) // if (orderGroupButton) { // setTimeout(() => { // orderGroupButton.click() // }, 1000) // } } catch (error) { console.error('抓取元素时出错:', error) } } // 监控按钮状态变化 function monitorButtonState(button) { console.log( '[ TemperMonkey ]初始按钮状态:', JSON.stringify({ disabled: button.disabled, text: button.textContent.trim(), classList: Array.from(button.classList), }), ) // 创建观察者监控按钮属性变化 const attributeObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'disabled') { console.log( '[ TemperMonkey ]按钮禁用状态变化:', JSON.stringify({ nowDisabled: button.disabled, time: new Date().toLocaleTimeString(), }), ) } }) }) // 观察按钮的属性变化 attributeObserver.observe(button, { attributes: true }) // 可以在这里添加其他逻辑,比如当按钮启用时自动点击 // if (!button.disabled) { // button.click(); // } } // 创建插入按钮HTML结构 function createInsertHTML(targetUl) { // 创建新按钮 const insertButton = document.createElement('button') insertButton.type = 'button' // 复制现有按钮的样式类,使新按钮外观一致 insertButton.className = 'el-button el-tooltip el-button--primary' insertButton.innerHTML = '<span> 自定义 </span>' // 给新按钮添加点击事件 insertButton.addEventListener('click', function () { alert('自定义按钮被点击了!') // 这里可以添加按钮的具体功能 }) // 创建新的li元素来包裹按钮 const insertLi = document.createElement('li') // 保持与其他元素一致的属性 insertLi.setAttribute('data-v-99e0d0b4', '') insertLi.setAttribute('style', 'margin-left: 10px ') insertLi.appendChild(insertButton) targetUl.appendChild(insertLi) } /************************************************************************************************************************/ // 初始化网络请求 function initialHttpUtils() { /** * 网络请求工具类 */ const HttpUtils = { /** * 通用请求方法 * @param {Object} options - 请求配置 * @param {string} options.url - 请求地址 * @param {string} [options.method='GET'] - 请求方法 * @param {Object} [options.headers] - 请求头 * @param {Object|string} [options.data] - 请求数据 * @param {number} [options.timeout=10000] - 超时时间(ms) * @param {boolean} [options.responseType='json'] - 响应数据类型 * @returns {Promise} - 返回Promise对象 */ request: function (options) { // 默认配置 const defaults = { method: 'GET', headers: { 'Content-Type': 'application/json', // 可添加默认的User-Agent等头信息 }, timeout: 10000, responseType: 'json', } // 合并配置 const config = { ...defaults, ...options } return new Promise((resolve, reject) => { // 处理请求数据 let postData = config.data if ( postData && typeof postData === 'object' && config.headers['Content-Type'] === 'application/json' ) { postData = JSON.stringify(postData) } // 使用GM_xmlhttpRequest(油猴提供的跨域请求API) const xhr = GM_xmlhttpRequest({ url: config.url, method: config.method, headers: config.headers, data: postData, timeout: config.timeout, responseType: config.responseType, // 成功回调 onload: function (response) { // 处理不同状态码 if (response.status >= 200 && response.status < 300) { // 尝试解析JSON let result = response.response if (typeof result === 'string' && config.responseType === 'json') { try { result = JSON.parse(result) } catch (e) { console.warn('响应数据不是有效的JSON') } } resolve({ data: result, status: response.status, statusText: response.statusText, }) } else { reject(new Error(`请求失败: ${response.status} ${response.statusText}`)) } }, // 错误回调 onerror: function (error) { reject(new Error(`网络错误: ${error.message || '未知错误'}`)) }, // 超时回调 ontimeout: function () { reject(new Error(`请求超时 (${config.timeout}ms)`)) }, }) }) }, /** * GET请求 * @param {string} url - 请求地址 * @param {Object} [params] - 请求参数 * @param {Object} [options] - 其他配置 * @returns {Promise} */ get: function (url, params = {}, options = {}) { // 拼接查询参数 const queryString = new URLSearchParams(params).toString() const fullUrl = queryString ? `${url}?${queryString}` : url return this.request({ ...options, url: fullUrl, method: 'GET', }) }, /** * POST请求 * @param {string} url - 请求地址 * @param {Object|string} [data] - 请求数据 * @param {Object} [options] - 其他配置 * @returns {Promise} */ post: function (url, data = {}, options = {}) { return this.request({ ...options, url, method: 'POST', data, }) }, /** * PUT请求 * @param {string} url - 请求地址 * @param {Object|string} [data] - 请求数据 * @param {Object} [options] - 其他配置 * @returns {Promise} */ put: function (url, data = {}, options = {}) { return this.request({ ...options, url, method: 'PUT', data, }) }, /** * DELETE请求 * @param {string} url - 请求地址 * @param {Object} [params] - 请求参数 * @param {Object} [options] - 其他配置 * @returns {Promise} */ delete: function (url, params = {}, options = {}) { const queryString = new URLSearchParams(params).toString() const fullUrl = queryString ? `${url}?${queryString}` : url return this.request({ ...options, url: fullUrl, method: 'DELETE', }) }, } // 暴露到window对象供其他脚本使用(如果需要) window.HttpUtils = HttpUtils // 使用示例 ;(async function () { try { const url = 'https://newerp.xjzcctv.com/api/oauth/getLoginConfig' const params = { _t: 1758511201883 } // GET请求示例 const getResult = await HttpUtils.get(url, params) console.log('[ TemperMonkey-URL-GET ]', url) console.log('[ TemperMonkey-RESPONSE ]', getResult.data) // POST请求示例 // const postResult = await HttpUtils.post('https://api.example.com/submit', { // username: 'test', // content: '这是测试数据', // }) // console.log('POST请求结果:', postResult.data) } catch (error) { console.error('请求出错:', error.message) } })() } /************************************************************************************************************************/ // 添加拦截器 function addInterceptor() { ;(() => { function addXMLRequestCallback(callback) { // 是一个劫持的函数 var oldSend, i if (XMLHttpRequest.callbacks) { // 判断XMLHttpRequest对象下是否存在回调列表,存在就push一个回调的函数 // we've already overridden send() so just add the callback XMLHttpRequest.callbacks.push(callback) } else { // create a callback queue XMLHttpRequest.callbacks = [callback] // 如果不存在则在xmlhttprequest函数下创建一个回调列表 // store the native send() oldSend = XMLHttpRequest.prototype.send // 获取旧xml的send函数,并对其进行劫持 // override the native send() XMLHttpRequest.prototype.send = function () { // process the callback queue // the xhr instance is passed into each callback but seems pretty useless // you can't tell what its destination is or call abort() without an error // so only really good for logging that a request has happened // I could be wrong, I hope so... // EDIT: I suppose you could override the onreadystatechange handler though for (i = 0; i < XMLHttpRequest.callbacks.length; i++) { XMLHttpRequest.callbacks[i](this) } // 循环回调xml内的回调函数 // call the native send() oldSend.apply(this, arguments) // 由于我们获取了send函数的引用,并且复写了send函数,这样我们在调用原send的函数的时候,需要对其传入引用,而arguments是传入的参数 } } } // e.g. addXMLRequestCallback(function (xhr) { // 调用劫持函数,填入一个function的回调函数 // 回调函数监听了对xhr调用了监听load状态,并且在触发的时候再次调用一个function,进行一些数据的劫持以及修改 xhr.addEventListener('load', function () { if (xhr.readyState == 4 && xhr.status == 200) { // console.log('[ TemperMonkey-xhr ]', xhr) console.log('[ TemperMonkey-URL ]', xhr.responseURL) console.log('[ TemperMonkey-RESPONSE ]', xhr.response) } }) }) })() } /************************************************************************************************************************/ // 添加监听器 async function addListener() { // 监听URL哈希变化(对于基于哈希的路由) window.addEventListener('hashchange', function () { console.log('[ TemperMonkey ]hashchange') }) // 监听URL的popstate事件(对于HTML5 History API路由) window.addEventListener('popstate', function () { console.log('[ TemperMonkey ]popstate') }) window.addEventListener('replaceState', function (e) { console.log('[ TemperMonkey ]replaceState') }) var _wr = function (type) { var orig = history[type] return function () { var rv = orig.apply(this, arguments) var e = new Event(type) e.arguments = arguments window.dispatchEvent(e) return rv } } history.pushState = _wr('pushState') window.addEventListener('pushState', async function (e) { console.log('[ TemperMonkey ]pushState' + JSON.stringify(e)) //出口管理-排除管理-订单分析页面URL if ( e.arguments['2'] === 'https://unionwms.jdl.com/default#/app-v/wms-outbound-view/productionScheduling/orderAnalysis/orderAnalysis' ) { // 启动脚本 performAction() } }) } /************************************************************************************************************************/ // 等待元素加载的函数,通过元素文本过滤 function waitForElementWithText(selector, options = {}) { // 默认配置 const defaults = { timeout: 15000, text: null, // 按钮文本内容过滤 exactMatch: true, // 是否精确匹配文本 } // 合并配置 const config = { ...defaults, ...options } // 验证选择器合法性 if (typeof selector !== 'string' || selector.trim() === '') { return Promise.reject(new Error('无效的选择器: 必须提供非空字符串')) } // 如果指定了文本过滤,但选择器不是按钮,则自动限制为按钮元素 const targetSelector = selector.includes('button') ? selector : `${selector} button` return new Promise((resolve, reject) => { let isResolved = false // 检查元素是否符合条件(包括文本过滤) const checkElement = () => { const elements = document.querySelectorAll(targetSelector) for (const element of elements) { // 如果不需要文本过滤,直接返回第一个匹配元素 if (!config.text) { return element } // 处理文本过滤 const elementText = element.textContent.trim() const targetText = config.text.trim() // 精确匹配或包含匹配 if ( (config.exactMatch && elementText === targetText) || (!config.exactMatch && elementText.includes(targetText)) ) { return element } } return null } const observer = new MutationObserver(() => { // 已找到元素则不再处理 if (isResolved) return const element = checkElement() if (element) { isResolved = true observer.disconnect() resolve(element) } }) try { // 开始观察整个文档的变化 observer.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true, // 监听文本内容变化 }) // 超时处理 const timeoutId = setTimeout(() => { if (!isResolved) { isResolved = true observer.disconnect() const textFilterInfo = config.text ? `,文本为"${config.text}"` : '' reject( new Error( `超时:在${config.timeout}ms内未找到匹配选择器 "${selector}"${textFilterInfo} 的元素`, ), ) } }, config.timeout) // 立即检查一次 const element = checkElement() if (element) { isResolved = true clearTimeout(timeoutId) observer.disconnect() resolve(element) } } catch (error) { // 捕获观察过程中的异常 observer.disconnect() reject(new Error(`观察元素时发生错误: ${error.message}`)) } }) // 使用示例: // 1. 查找包含"组波"文本的按钮 // waitForElement('', { text: '组波' }) // .then(button => console.log('找到按钮:', button)) // .catch(error => console.error('错误:', error)); // // 2. 查找包含"查询"文本的主要按钮 // waitForElement('.el-button--primary', { text: '查询', exactMatch: false }) // .then(button => console.log('找到按钮:', button)) // .catch(error => console.error('错误:', error)); } // 等待元素加载并返回的函数,通过元素文字过滤 function waitForElement(selector, options = {}) { const defaults = { timeout: 15000, text: null, exactMatch: true, } const config = { ...defaults, ...options } if (typeof selector !== 'string' || selector.trim() === '') { return Promise.reject(new Error('无效的选择器: 必须提供非空字符串')) } return new Promise((resolve, reject) => { let isResolved = false const checkElement = () => { const elements = document.querySelectorAll(selector) for (const element of elements) { if (!config.text) { return element } const elementText = element.textContent.trim() const targetText = config.text.trim() if ( (config.exactMatch && elementText === targetText) || (!config.exactMatch && elementText.includes(targetText)) ) { return element } } return null } const observer = new MutationObserver(() => { if (isResolved) return const element = checkElement() if (element) { isResolved = true observer.disconnect() resolve(element) } }) try { observer.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true, }) const timeoutId = setTimeout(() => { if (!isResolved) { isResolved = true observer.disconnect() const textFilterInfo = config.text ? `,文本为"${config.text}"` : '' reject( new Error( `超时:在${config.timeout}ms内未找到匹配选择器 "${selector}"${textFilterInfo} 的元素`, ), ) } }, config.timeout) const element = checkElement() if (element) { isResolved = true clearTimeout(timeoutId) observer.disconnect() resolve(element) } } catch (error) { observer.disconnect() reject(new Error(`观察元素时发生错误: ${error.message}`)) } }) } /************************************************************************************************************************/