NewGoldenState Userscript

采集电商订单

目前為 2025-09-24 提交的版本,檢視 最新版本

// ==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}`))
    }
  })
}


/************************************************************************************************************************/