NewGoldenState Userscript

采集电商订单

Pada tanggal 24 September 2025. Lihat %(latest_version_link).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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


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