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