// ==UserScript== // @name link工具 // @namespace http://tampermonkey.net/ // @version 0.9.7.2 // @description 点击复制,提取链接 // @author lwj // @match *://m.linkmcn.com/* // @match https://m.linkmcn.com/tableCard/redirect* // @match *://detail.tmall.com/item* // @match *://item.taobao.com/item* // @match *://chaoshi.detail.tmall.com/item* // @match *://traveldetail.fliggy.com/item* // @match *://detail.tmall.hk/hk/item* // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @downloadURL none // ==/UserScript== (function () { // 版本号 var versionTitleTxt = 'Version 0.9.7.2'; // 检查当前页面 URL 是否匹配指定的网址 var isHomeURL = function () { var currentURL = window.location.href; return currentURL.indexOf("https://m.linkmcn.com/#/live/plan?select=") !== -1; }; // 从 localStorage 中获取上一次的 URL,如果没有则设置为空字符串 let lastCurrentURL = localStorage.getItem('lastCurrentURL') || ''; var isTableCardURL = function () { return window.location.href.indexOf("https://m.linkmcn.com/#/live/plan/tableCard/") !== -1; }; var isBatchPrintURL = function () { return window.location.href.indexOf("https://m.linkmcn.com/#/live/plan/batchPrint") !== -1; }; var isTmallItemURL = function () { var url = window.location.href; // 检查URL是否包含任何指定的域名或路径 return ( url.indexOf("https://detail.tmall.com/item") !== -1 || url.indexOf("https://detail.tmall.hk/hk/item") !== -1 || url.indexOf("https://item.taobao.com/item") !== -1 || url.indexOf("https://chaoshi.detail.tmall.com/item") !== -1 || url.indexOf("https://traveldetail.fliggy.com/item") !== -1 ); } var isRedirectUrl = function () { return window.location.href.indexOf("https://item.taobao.com/item.htm?id=682878335608&link_redirectId=") !== -1; } var notificationTimer; // 通知计时器 var isHiding = false; // 标志,表示是否正在隐藏通知 let countSort_notificationTimeout = null;// 排序提示定时器 let temp_itemId = ''; class ToggleButtonComponent { constructor(localStorageKey, buttonText, dropdownContainer, useSpecialSwitch = 0, defaultState = false) { this.localStorageKey = localStorageKey; this.switchState = localStorage.getItem(this.localStorageKey) === null ? defaultState : localStorage.getItem(this.localStorageKey) === 'true'; this.buttonText = buttonText; this.dropdownContainer = dropdownContainer; this.useSpecialSwitch = useSpecialSwitch === 1; this.createComponent(); } createComponent() { // 创建按钮容器 this.buttonContainer = document.createElement('div'); this.buttonContainer.classList.add('flex', 'items-center', 'dropdown-item'); this.buttonContainer.style.cssText = 'padding: 12px 16px; user-select: none; cursor: pointer; display: flex; justify-content: space-between; align-items: center;'; if (this.buttonText === '纯净模式') { this.buttonContainer.style.paddingTop = '0px'; } // 创建按钮文本 this.buttonTextElement = document.createElement('span'); this.buttonTextElement.textContent = this.buttonText; this.buttonTextElement.classList.add('lh-22'); this.buttonContainer.appendChild(this.buttonTextElement); if (this.useSpecialSwitch) { // 创建特殊开关样式按钮 this.createSpecialSwitch(); } else { // 创建普通按钮 this.createStandardButton(); } // 将按钮容器添加到下拉容器中 this.dropdownContainer.appendChild(this.buttonContainer); // 创建对应的空的子页面并设置样式 this.secondaryContent = document.createElement('div'); this.secondaryContent.id = this.localStorageKey + '-secondary-content'; this.secondaryContent.style.cssText = 'margin: 0px 10px; display: none; padding: 10px 12px; background: #E9E9E9; border: 1px solid #D2D2D2; border-radius: 10px;'; // 将子页面添加到按钮容器的下方 this.dropdownContainer.appendChild(this.secondaryContent); } createStandardButton() { this.button = document.createElement('button'); this.button.id = 'main_standardFunShowButton'; this.button.style.cssText = 'background: none; border: none; font-size: 16px; cursor: pointer; transition: transform 0.3s; margin: 0px 6px'; this.button.appendChild(createSVGIcon()); this.buttonContainer.appendChild(this.button); // 绑定点击事件 this.button.addEventListener('click', () => this.handleStandardButtonClick()); // 给标题绑定模拟点击开关事件 this.buttonTextElement.addEventListener('click', () => this.titleToSwitchClick()); } handleStandardButtonClick() { // 切换二级页面显示状态 const secondaryContent = document.getElementById(this.localStorageKey + '-secondary-content'); if (secondaryContent) { secondaryContent.style.display = secondaryContent.style.display === 'block' ? 'none' : 'block'; } else { console.log(`${this.buttonText} 二级页面异常`); } // 旋转按钮图标 const icon = this.button.querySelector('svg'); const rotation = icon.style.transform === 'rotate(90deg)' ? 'rotate(0deg)' : 'rotate(90deg)'; icon.style.transform = rotation; } createSpecialSwitch() { this.button = document.createElement('button'); this.button.innerHTML = '
标签或其他容器标签 if (element.tagName === 'p') { // 找到所有 标签 const spans = element.querySelectorAll('span'); spans.forEach(span => { // 创建一个临时的文本节点,用于插入 标签的内容 const textNode = document.createTextNode(span.textContent); // 用文本节点替换 标签 span.parentNode.replaceChild(textNode, span); }); } // 递归处理子节点 for (let i = 0; i < element.children.length; i++) { removeStyles(element.children[i]); } } } // 移除所有元素的 style 和 data-mce-style 属性,并替换 标签为纯文本 function removeStyles(element) { // 遍历所有的 和 标签 const elements = element.querySelectorAll('p, span'); elements.forEach(el => { // 删除 style 和 data-mce-style 属性 el.removeAttribute('style'); el.removeAttribute('data-mce-style'); // 如果是 标签,用它的文本内容替换 if (el.tagName.toLowerCase() === 'span' && !numericRegex.test(el.textContent)) { // 用文本节点替换 标签 const textNode = document.createTextNode(el.textContent); el.parentNode.replaceChild(textNode, el); } }); // 递归处理所有子元素,移除样式属性 for (let i = 0; i < element.children.length; i++) { removeStyles(element.children[i]); } } // 根据优先级排序colorMap,长文本优先 function sortColorMap(colorMap) { return colorMap.slice().sort((a, b) => b.regex.source.length - a.regex.source.length); } // 应用颜色到现有的 span 或创建新的 span function applyColor(span, color) { if (!span.getAttribute('data-mce-style')) { span.style.color = color; span.setAttribute('data-mce-style', `color: ${color};`); } } // 添加新样式 // 此函数用于向iframe文档中的所有元素添加新的样式。 // 参数: // - iframeDocument: iframe的文档对象 // - colorMap: 包含正则表达式和对应颜色的映射对象 function addNewStyles(iframeDocument, colorMap) { const ps = iframeDocument.querySelectorAll('p'); ps.forEach(p => { const innerHTML = p.innerHTML; let newInnerHTML = ''; // 先按照标签进行分割 const spanParts = innerHTML.split(/(]*>.*?<\/span>)/); spanParts.forEach(spanPart => { if (spanPart.match(/^]*>.*<\/span>$/)) { // 如果是标签包裹的部分,直接添加到 newInnerHTML newInnerHTML += spanPart; } else { // 处理不包含的部分 const parts = spanPart.split(/(| )/); parts.forEach(part => { if (part.match(/(| )/)) { // 如果是或 ,直接添加到 newInnerHTML newInnerHTML += part; } else { let styledPart = part; const sortedColorMap = sortColorMap(colorMap); sortedColorMap.forEach(map => { if (map.regex.test(part)) { const color = map.color; // 仅对不在标签内的内容进行着色处理 const match = map.regex.exec(part); if (match) { const span = document.createElement('span'); span.innerHTML = match[0]; applyColor(span, color); styledPart = part.replace(map.regex, span.outerHTML); } } }); newInnerHTML += styledPart; } }); } }); p.innerHTML = newInnerHTML; }); } function applyPrefixSuffixColor(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); ps.forEach(p => { const innerHTML = p.innerHTML; let newInnerHTML = ''; const parts = innerHTML.split(/(| )/); parts.forEach(part => { const testApply = part.indexOf('折扣'); if (testApply !== -1 || priceGameplayRegex.test(part.textContent)) { newInnerHTML += part; return; // 跳过折扣行 } const colonIndex = part.indexOf(':'); if (colonIndex !== -1) { const prefix = part.substring(0, colonIndex + 1); // 包含“:” const suffix = part.substring(colonIndex + 1); // 创建临时 div 用于获取后缀的纯文本 const tempDiv = document.createElement('div'); tempDiv.innerHTML = suffix; const plainTextSuffix = tempDiv.textContent || tempDiv.innerText || ""; // 检查后缀并应用颜色 let styledSuffix = suffix; const suffixSpan = document.createElement('span'); suffixSpan.innerHTML = suffix; if (numericRegex.test(plainTextSuffix)) { applyColor(suffixSpan, colorLibrary.red); styledSuffix = suffixSpan.outerHTML; } else { applyColor(suffixSpan, colorLibrary.gray); styledSuffix = suffixSpan.outerHTML; } // 创建前缀 span 并应用颜色 const prefixSpan = document.createElement('span'); prefixSpan.innerHTML = prefix; if (numericRegex.test(plainTextSuffix)) { applyColor(prefixSpan, colorLibrary.blue); } else { applyColor(prefixSpan, colorLibrary.gray); } newInnerHTML += prefixSpan.outerHTML + styledSuffix; } else { newInnerHTML += part; } }); p.innerHTML = newInnerHTML; }); } // 危险内容替换函数 function replaceTextContent(iframeDocument, replacementMap) { // 获取所有的 标签 const ps = iframeDocument.querySelectorAll('p'); // 遍历每一个 标签 ps.forEach(p => { let innerHTML = p.innerHTML; let newInnerHTML = innerHTML; // 遍历 JSON 中的每个键值对 Object.keys(replacementMap).forEach(key => { const value = replacementMap[key]; // 使用正则表达式替换所有匹配的文本 const regex = new RegExp(key, 'g'); // 'g' 标志用于全局替换 newInnerHTML = newInnerHTML.replace(regex, value); }); // 将新的 HTML 内容赋给 标签 p.innerHTML = newInnerHTML; }); } const activateIframeAndModifyStyles = activateElementId => { const iframe = qyeryIframeForId(activateElementId); if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument) { // 清除原有的样式 if (sonMain_drawColor.getSwitchState()) { removeStyles(iframeDocument.body); } if (sonMain_calculator.getSwitchState()) { findAndCalculateExpressions(iframeDocument, calculate); } if (sonMain_smartReplace.getSwitchState()) { replaceTextContent(iframeDocument, replacementMap); } if (sonMain_drawColor.getSwitchState()) { // 第一行红色标记 if (activateElementId === 'livePrice') { if (sonMain_smartReplace.getSwitchState()) { autoWriteAvgPrice(iframeDocument); } modifyFirstPTagAndSpans(iframeDocument); applyPrefixSuffixColor(iframeDocument); } if (activateElementId === 'goodsName') { // 检查标题是否含有预售字样,检查是否是预售品 // console.log("check_isHavePreSale_in_goodName: " + check_isHavePreSale_in_goodName(iframeDocument)); // console.log("check_isHavePreSaleContent: " + check_isHavePreSaleContent()); if (!check_isHavePreSale_havePreSale(iframeDocument) && check_isHavePreSaleContent()) { addContent_PreSale(iframeDocument); } } // 规格首行非sku描述行蓝色 if (activateElementId === 'skuDesc') { skuDescFirstLineBlue(iframeDocument); } if (activateElementId === 'liveGameplay') { // 检查是否含有预售字样,检查是否是预售品 if (!check_isHavePreSaleContent(activateElementId) && check_isHavePreSaleContent()) { addContent_PreSaleTime(iframeDocument); } } // 获取对应的颜色映射 const colorMap = colorMaps[activateElementId]; const colorMap2 = colorMaps2[activateElementId]; const colorMap3 = colorMaps3[activateElementId]; if (colorMap) { if (activateElementId === 'inventory') { handleInventoryStyles(iframeDocument); } else if (activateElementId === 'preSetInventory') { handlePreSetInventoryStyles(iframeDocument); } else { if (colorMap) { addNewStyles(iframeDocument, colorMap); } if (colorMap2) { addNewStyles(iframeDocument, colorMap2); } if (colorMap3) { addNewStyles(iframeDocument, colorMap3); } } } else { console.error('未找到对应的颜色映射。'); } removeDataProcessed(iframeDocument); isMarioShow.push(activateElementId); } } else { console.error('无法访问iframe文档。'); } } else { console.error('未找到iframe元素。'); } }; const qyeryIframeForId = activateElementId => { const activateElement = document.querySelector(`#${activateElementId} .link-node-hover-text-container`); if (activateElement) { activateElement.click(); activateElement.focus(); return document.querySelector(`#${activateElementId} .tox-edit-area iframe`); } else { console.error('未找到激活元素。'); return null; } }; const removeDataProcessed = doc => { const replaceElements = doc.querySelectorAll('[data-replace="true"]'); replaceElements.forEach(element => { const parentElement = element.parentElement; if (parentElement.tagName.toLowerCase() === 'p') { // 检查 标签是否只有一个子元素,并且是当前的 const hasOnlySpanChild = parentElement.children.length === 1 && parentElement.children[0] === element; // 获取父 元素的纯文本内容(不包括子元素) const parentText = parentElement.textContent.trim(); if (hasOnlySpanChild && parentText === '无效内容') { // 如果 标签没有其他文本内容,移除整个 标签 parentElement.remove(); } else { // 否则,清空 的文本内容 element.textContent = ''; } } else { // 如果父元素不是 ,清空 的文本内容 element.textContent = ''; } }); }; // 规格首行非sku描述行蓝色 const skuDescFirstLineBlue = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.blue); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(spanTag, colorLibrary.blue); } }); } }; // 到手价数字行红色 const modifyFirstPTagAndSpans = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (numericRegex.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.red); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (numericRegex.test(spanTag.textContent)) { applyColor(spanTag, colorLibrary.red); } }); } }; function isNotIframeChineseName(text) { // 定义需要比较的内容数组 const contents = [ "商品标题", "规格信息", "直播价", "直播玩法/优惠方式", "预设库存", "现货库存" ]; // 遍历数组寻找匹配项 for (let i = 0; i < contents.length; i++) { if (text === contents[i]) { // 如果找到匹配项,返回其索引+1 return i + 1; } } // 如果没有找到匹配项,返回0 return 0; } // 检查到手价是否包含预售字样 function check_isHavePreSaleContent(idName = 'livePrice') { // const livePriceDoc = document.querySelector(`#livePrice`); var livePriceDoc = document.getElementById(idName); if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { livePriceDoc = body; } } const currentHTML = livePriceDoc.innerText; if (currentHTML.includes('定金') || currentHTML.includes('尾款')) { // console.log('到手价包含预售字样'); return true; } else { // console.log('到手价不包含预售字样'); return false; } } } function check_isHavePreSale_havePreSale(iframeDocument) { const iframe = iframeDocument.querySelector('iframe'); var body = iframeDocument.body; if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument.body && !isNotIframeChineseName(iframeDocument.body.innerText)) { body = iframeDocument.body; } } // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; if (currentHTML.includes('预售')) { return true; } else { return false; } } function addContent_PreSale(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = currentHTML + `预售`; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } function addContent_PreSaleTime(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面预售时间信息 const updatedHTML = currentHTML + replacementMap.定金尾款时间; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } // 预设库存样式修改 const handlePreSetInventoryStyles = doc => { function check_content(content) { if (!numericRegex.test(content)) { if (content.includes('不可控')) { return false; } else { return true; } } else { return false; } } const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { if (check_content(pTag.textContent)) { pTag.textContent = '拉满'; const spanTags = pTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (check_content(spanTag.textContent)) { spanTag.textContent = '拉满'; } }); } }); }; // 现货库存样式修改 const handleInventoryStyles = doc => { let firstPTagFound = false; const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { // 获取 标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取 标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的 标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原 标签位置插入新的 标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的 标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();
和 标签 const elements = element.querySelectorAll('p, span'); elements.forEach(el => { // 删除 style 和 data-mce-style 属性 el.removeAttribute('style'); el.removeAttribute('data-mce-style'); // 如果是 标签,用它的文本内容替换 if (el.tagName.toLowerCase() === 'span' && !numericRegex.test(el.textContent)) { // 用文本节点替换 标签 const textNode = document.createTextNode(el.textContent); el.parentNode.replaceChild(textNode, el); } }); // 递归处理所有子元素,移除样式属性 for (let i = 0; i < element.children.length; i++) { removeStyles(element.children[i]); } } // 根据优先级排序colorMap,长文本优先 function sortColorMap(colorMap) { return colorMap.slice().sort((a, b) => b.regex.source.length - a.regex.source.length); } // 应用颜色到现有的 span 或创建新的 span function applyColor(span, color) { if (!span.getAttribute('data-mce-style')) { span.style.color = color; span.setAttribute('data-mce-style', `color: ${color};`); } } // 添加新样式 // 此函数用于向iframe文档中的所有元素添加新的样式。 // 参数: // - iframeDocument: iframe的文档对象 // - colorMap: 包含正则表达式和对应颜色的映射对象 function addNewStyles(iframeDocument, colorMap) { const ps = iframeDocument.querySelectorAll('p'); ps.forEach(p => { const innerHTML = p.innerHTML; let newInnerHTML = ''; // 先按照标签进行分割 const spanParts = innerHTML.split(/(]*>.*?<\/span>)/); spanParts.forEach(spanPart => { if (spanPart.match(/^]*>.*<\/span>$/)) { // 如果是标签包裹的部分,直接添加到 newInnerHTML newInnerHTML += spanPart; } else { // 处理不包含的部分 const parts = spanPart.split(/(| )/); parts.forEach(part => { if (part.match(/(| )/)) { // 如果是或 ,直接添加到 newInnerHTML newInnerHTML += part; } else { let styledPart = part; const sortedColorMap = sortColorMap(colorMap); sortedColorMap.forEach(map => { if (map.regex.test(part)) { const color = map.color; // 仅对不在标签内的内容进行着色处理 const match = map.regex.exec(part); if (match) { const span = document.createElement('span'); span.innerHTML = match[0]; applyColor(span, color); styledPart = part.replace(map.regex, span.outerHTML); } } }); newInnerHTML += styledPart; } }); } }); p.innerHTML = newInnerHTML; }); } function applyPrefixSuffixColor(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); ps.forEach(p => { const innerHTML = p.innerHTML; let newInnerHTML = ''; const parts = innerHTML.split(/(| )/); parts.forEach(part => { const testApply = part.indexOf('折扣'); if (testApply !== -1 || priceGameplayRegex.test(part.textContent)) { newInnerHTML += part; return; // 跳过折扣行 } const colonIndex = part.indexOf(':'); if (colonIndex !== -1) { const prefix = part.substring(0, colonIndex + 1); // 包含“:” const suffix = part.substring(colonIndex + 1); // 创建临时 div 用于获取后缀的纯文本 const tempDiv = document.createElement('div'); tempDiv.innerHTML = suffix; const plainTextSuffix = tempDiv.textContent || tempDiv.innerText || ""; // 检查后缀并应用颜色 let styledSuffix = suffix; const suffixSpan = document.createElement('span'); suffixSpan.innerHTML = suffix; if (numericRegex.test(plainTextSuffix)) { applyColor(suffixSpan, colorLibrary.red); styledSuffix = suffixSpan.outerHTML; } else { applyColor(suffixSpan, colorLibrary.gray); styledSuffix = suffixSpan.outerHTML; } // 创建前缀 span 并应用颜色 const prefixSpan = document.createElement('span'); prefixSpan.innerHTML = prefix; if (numericRegex.test(plainTextSuffix)) { applyColor(prefixSpan, colorLibrary.blue); } else { applyColor(prefixSpan, colorLibrary.gray); } newInnerHTML += prefixSpan.outerHTML + styledSuffix; } else { newInnerHTML += part; } }); p.innerHTML = newInnerHTML; }); } // 危险内容替换函数 function replaceTextContent(iframeDocument, replacementMap) { // 获取所有的 标签 const ps = iframeDocument.querySelectorAll('p'); // 遍历每一个 标签 ps.forEach(p => { let innerHTML = p.innerHTML; let newInnerHTML = innerHTML; // 遍历 JSON 中的每个键值对 Object.keys(replacementMap).forEach(key => { const value = replacementMap[key]; // 使用正则表达式替换所有匹配的文本 const regex = new RegExp(key, 'g'); // 'g' 标志用于全局替换 newInnerHTML = newInnerHTML.replace(regex, value); }); // 将新的 HTML 内容赋给 标签 p.innerHTML = newInnerHTML; }); } const activateIframeAndModifyStyles = activateElementId => { const iframe = qyeryIframeForId(activateElementId); if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument) { // 清除原有的样式 if (sonMain_drawColor.getSwitchState()) { removeStyles(iframeDocument.body); } if (sonMain_calculator.getSwitchState()) { findAndCalculateExpressions(iframeDocument, calculate); } if (sonMain_smartReplace.getSwitchState()) { replaceTextContent(iframeDocument, replacementMap); } if (sonMain_drawColor.getSwitchState()) { // 第一行红色标记 if (activateElementId === 'livePrice') { if (sonMain_smartReplace.getSwitchState()) { autoWriteAvgPrice(iframeDocument); } modifyFirstPTagAndSpans(iframeDocument); applyPrefixSuffixColor(iframeDocument); } if (activateElementId === 'goodsName') { // 检查标题是否含有预售字样,检查是否是预售品 // console.log("check_isHavePreSale_in_goodName: " + check_isHavePreSale_in_goodName(iframeDocument)); // console.log("check_isHavePreSaleContent: " + check_isHavePreSaleContent()); if (!check_isHavePreSale_havePreSale(iframeDocument) && check_isHavePreSaleContent()) { addContent_PreSale(iframeDocument); } } // 规格首行非sku描述行蓝色 if (activateElementId === 'skuDesc') { skuDescFirstLineBlue(iframeDocument); } if (activateElementId === 'liveGameplay') { // 检查是否含有预售字样,检查是否是预售品 if (!check_isHavePreSaleContent(activateElementId) && check_isHavePreSaleContent()) { addContent_PreSaleTime(iframeDocument); } } // 获取对应的颜色映射 const colorMap = colorMaps[activateElementId]; const colorMap2 = colorMaps2[activateElementId]; const colorMap3 = colorMaps3[activateElementId]; if (colorMap) { if (activateElementId === 'inventory') { handleInventoryStyles(iframeDocument); } else if (activateElementId === 'preSetInventory') { handlePreSetInventoryStyles(iframeDocument); } else { if (colorMap) { addNewStyles(iframeDocument, colorMap); } if (colorMap2) { addNewStyles(iframeDocument, colorMap2); } if (colorMap3) { addNewStyles(iframeDocument, colorMap3); } } } else { console.error('未找到对应的颜色映射。'); } removeDataProcessed(iframeDocument); isMarioShow.push(activateElementId); } } else { console.error('无法访问iframe文档。'); } } else { console.error('未找到iframe元素。'); } }; const qyeryIframeForId = activateElementId => { const activateElement = document.querySelector(`#${activateElementId} .link-node-hover-text-container`); if (activateElement) { activateElement.click(); activateElement.focus(); return document.querySelector(`#${activateElementId} .tox-edit-area iframe`); } else { console.error('未找到激活元素。'); return null; } }; const removeDataProcessed = doc => { const replaceElements = doc.querySelectorAll('[data-replace="true"]'); replaceElements.forEach(element => { const parentElement = element.parentElement; if (parentElement.tagName.toLowerCase() === 'p') { // 检查 标签是否只有一个子元素,并且是当前的 const hasOnlySpanChild = parentElement.children.length === 1 && parentElement.children[0] === element; // 获取父 元素的纯文本内容(不包括子元素) const parentText = parentElement.textContent.trim(); if (hasOnlySpanChild && parentText === '无效内容') { // 如果 标签没有其他文本内容,移除整个 标签 parentElement.remove(); } else { // 否则,清空 的文本内容 element.textContent = ''; } } else { // 如果父元素不是 ,清空 的文本内容 element.textContent = ''; } }); }; // 规格首行非sku描述行蓝色 const skuDescFirstLineBlue = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.blue); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(spanTag, colorLibrary.blue); } }); } }; // 到手价数字行红色 const modifyFirstPTagAndSpans = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (numericRegex.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.red); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (numericRegex.test(spanTag.textContent)) { applyColor(spanTag, colorLibrary.red); } }); } }; function isNotIframeChineseName(text) { // 定义需要比较的内容数组 const contents = [ "商品标题", "规格信息", "直播价", "直播玩法/优惠方式", "预设库存", "现货库存" ]; // 遍历数组寻找匹配项 for (let i = 0; i < contents.length; i++) { if (text === contents[i]) { // 如果找到匹配项,返回其索引+1 return i + 1; } } // 如果没有找到匹配项,返回0 return 0; } // 检查到手价是否包含预售字样 function check_isHavePreSaleContent(idName = 'livePrice') { // const livePriceDoc = document.querySelector(`#livePrice`); var livePriceDoc = document.getElementById(idName); if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { livePriceDoc = body; } } const currentHTML = livePriceDoc.innerText; if (currentHTML.includes('定金') || currentHTML.includes('尾款')) { // console.log('到手价包含预售字样'); return true; } else { // console.log('到手价不包含预售字样'); return false; } } } function check_isHavePreSale_havePreSale(iframeDocument) { const iframe = iframeDocument.querySelector('iframe'); var body = iframeDocument.body; if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument.body && !isNotIframeChineseName(iframeDocument.body.innerText)) { body = iframeDocument.body; } } // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; if (currentHTML.includes('预售')) { return true; } else { return false; } } function addContent_PreSale(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = currentHTML + `预售`; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } function addContent_PreSaleTime(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面预售时间信息 const updatedHTML = currentHTML + replacementMap.定金尾款时间; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } // 预设库存样式修改 const handlePreSetInventoryStyles = doc => { function check_content(content) { if (!numericRegex.test(content)) { if (content.includes('不可控')) { return false; } else { return true; } } else { return false; } } const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { if (check_content(pTag.textContent)) { pTag.textContent = '拉满'; const spanTags = pTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (check_content(spanTag.textContent)) { spanTag.textContent = '拉满'; } }); } }); }; // 现货库存样式修改 const handleInventoryStyles = doc => { let firstPTagFound = false; const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { // 获取 标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取 标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的 标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原 标签位置插入新的 标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的 标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();
元素添加新的样式。 // 参数: // - iframeDocument: iframe的文档对象 // - colorMap: 包含正则表达式和对应颜色的映射对象 function addNewStyles(iframeDocument, colorMap) { const ps = iframeDocument.querySelectorAll('p'); ps.forEach(p => { const innerHTML = p.innerHTML; let newInnerHTML = ''; // 先按照标签进行分割 const spanParts = innerHTML.split(/(]*>.*?<\/span>)/); spanParts.forEach(spanPart => { if (spanPart.match(/^]*>.*<\/span>$/)) { // 如果是标签包裹的部分,直接添加到 newInnerHTML newInnerHTML += spanPart; } else { // 处理不包含的部分 const parts = spanPart.split(/(| )/); parts.forEach(part => { if (part.match(/(| )/)) { // 如果是或 ,直接添加到 newInnerHTML newInnerHTML += part; } else { let styledPart = part; const sortedColorMap = sortColorMap(colorMap); sortedColorMap.forEach(map => { if (map.regex.test(part)) { const color = map.color; // 仅对不在标签内的内容进行着色处理 const match = map.regex.exec(part); if (match) { const span = document.createElement('span'); span.innerHTML = match[0]; applyColor(span, color); styledPart = part.replace(map.regex, span.outerHTML); } } }); newInnerHTML += styledPart; } }); } }); p.innerHTML = newInnerHTML; }); } function applyPrefixSuffixColor(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); ps.forEach(p => { const innerHTML = p.innerHTML; let newInnerHTML = ''; const parts = innerHTML.split(/(| )/); parts.forEach(part => { const testApply = part.indexOf('折扣'); if (testApply !== -1 || priceGameplayRegex.test(part.textContent)) { newInnerHTML += part; return; // 跳过折扣行 } const colonIndex = part.indexOf(':'); if (colonIndex !== -1) { const prefix = part.substring(0, colonIndex + 1); // 包含“:” const suffix = part.substring(colonIndex + 1); // 创建临时 div 用于获取后缀的纯文本 const tempDiv = document.createElement('div'); tempDiv.innerHTML = suffix; const plainTextSuffix = tempDiv.textContent || tempDiv.innerText || ""; // 检查后缀并应用颜色 let styledSuffix = suffix; const suffixSpan = document.createElement('span'); suffixSpan.innerHTML = suffix; if (numericRegex.test(plainTextSuffix)) { applyColor(suffixSpan, colorLibrary.red); styledSuffix = suffixSpan.outerHTML; } else { applyColor(suffixSpan, colorLibrary.gray); styledSuffix = suffixSpan.outerHTML; } // 创建前缀 span 并应用颜色 const prefixSpan = document.createElement('span'); prefixSpan.innerHTML = prefix; if (numericRegex.test(plainTextSuffix)) { applyColor(prefixSpan, colorLibrary.blue); } else { applyColor(prefixSpan, colorLibrary.gray); } newInnerHTML += prefixSpan.outerHTML + styledSuffix; } else { newInnerHTML += part; } }); p.innerHTML = newInnerHTML; }); } // 危险内容替换函数 function replaceTextContent(iframeDocument, replacementMap) { // 获取所有的 标签 const ps = iframeDocument.querySelectorAll('p'); // 遍历每一个 标签 ps.forEach(p => { let innerHTML = p.innerHTML; let newInnerHTML = innerHTML; // 遍历 JSON 中的每个键值对 Object.keys(replacementMap).forEach(key => { const value = replacementMap[key]; // 使用正则表达式替换所有匹配的文本 const regex = new RegExp(key, 'g'); // 'g' 标志用于全局替换 newInnerHTML = newInnerHTML.replace(regex, value); }); // 将新的 HTML 内容赋给 标签 p.innerHTML = newInnerHTML; }); } const activateIframeAndModifyStyles = activateElementId => { const iframe = qyeryIframeForId(activateElementId); if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument) { // 清除原有的样式 if (sonMain_drawColor.getSwitchState()) { removeStyles(iframeDocument.body); } if (sonMain_calculator.getSwitchState()) { findAndCalculateExpressions(iframeDocument, calculate); } if (sonMain_smartReplace.getSwitchState()) { replaceTextContent(iframeDocument, replacementMap); } if (sonMain_drawColor.getSwitchState()) { // 第一行红色标记 if (activateElementId === 'livePrice') { if (sonMain_smartReplace.getSwitchState()) { autoWriteAvgPrice(iframeDocument); } modifyFirstPTagAndSpans(iframeDocument); applyPrefixSuffixColor(iframeDocument); } if (activateElementId === 'goodsName') { // 检查标题是否含有预售字样,检查是否是预售品 // console.log("check_isHavePreSale_in_goodName: " + check_isHavePreSale_in_goodName(iframeDocument)); // console.log("check_isHavePreSaleContent: " + check_isHavePreSaleContent()); if (!check_isHavePreSale_havePreSale(iframeDocument) && check_isHavePreSaleContent()) { addContent_PreSale(iframeDocument); } } // 规格首行非sku描述行蓝色 if (activateElementId === 'skuDesc') { skuDescFirstLineBlue(iframeDocument); } if (activateElementId === 'liveGameplay') { // 检查是否含有预售字样,检查是否是预售品 if (!check_isHavePreSaleContent(activateElementId) && check_isHavePreSaleContent()) { addContent_PreSaleTime(iframeDocument); } } // 获取对应的颜色映射 const colorMap = colorMaps[activateElementId]; const colorMap2 = colorMaps2[activateElementId]; const colorMap3 = colorMaps3[activateElementId]; if (colorMap) { if (activateElementId === 'inventory') { handleInventoryStyles(iframeDocument); } else if (activateElementId === 'preSetInventory') { handlePreSetInventoryStyles(iframeDocument); } else { if (colorMap) { addNewStyles(iframeDocument, colorMap); } if (colorMap2) { addNewStyles(iframeDocument, colorMap2); } if (colorMap3) { addNewStyles(iframeDocument, colorMap3); } } } else { console.error('未找到对应的颜色映射。'); } removeDataProcessed(iframeDocument); isMarioShow.push(activateElementId); } } else { console.error('无法访问iframe文档。'); } } else { console.error('未找到iframe元素。'); } }; const qyeryIframeForId = activateElementId => { const activateElement = document.querySelector(`#${activateElementId} .link-node-hover-text-container`); if (activateElement) { activateElement.click(); activateElement.focus(); return document.querySelector(`#${activateElementId} .tox-edit-area iframe`); } else { console.error('未找到激活元素。'); return null; } }; const removeDataProcessed = doc => { const replaceElements = doc.querySelectorAll('[data-replace="true"]'); replaceElements.forEach(element => { const parentElement = element.parentElement; if (parentElement.tagName.toLowerCase() === 'p') { // 检查 标签是否只有一个子元素,并且是当前的 const hasOnlySpanChild = parentElement.children.length === 1 && parentElement.children[0] === element; // 获取父 元素的纯文本内容(不包括子元素) const parentText = parentElement.textContent.trim(); if (hasOnlySpanChild && parentText === '无效内容') { // 如果 标签没有其他文本内容,移除整个 标签 parentElement.remove(); } else { // 否则,清空 的文本内容 element.textContent = ''; } } else { // 如果父元素不是 ,清空 的文本内容 element.textContent = ''; } }); }; // 规格首行非sku描述行蓝色 const skuDescFirstLineBlue = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.blue); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(spanTag, colorLibrary.blue); } }); } }; // 到手价数字行红色 const modifyFirstPTagAndSpans = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (numericRegex.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.red); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (numericRegex.test(spanTag.textContent)) { applyColor(spanTag, colorLibrary.red); } }); } }; function isNotIframeChineseName(text) { // 定义需要比较的内容数组 const contents = [ "商品标题", "规格信息", "直播价", "直播玩法/优惠方式", "预设库存", "现货库存" ]; // 遍历数组寻找匹配项 for (let i = 0; i < contents.length; i++) { if (text === contents[i]) { // 如果找到匹配项,返回其索引+1 return i + 1; } } // 如果没有找到匹配项,返回0 return 0; } // 检查到手价是否包含预售字样 function check_isHavePreSaleContent(idName = 'livePrice') { // const livePriceDoc = document.querySelector(`#livePrice`); var livePriceDoc = document.getElementById(idName); if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { livePriceDoc = body; } } const currentHTML = livePriceDoc.innerText; if (currentHTML.includes('定金') || currentHTML.includes('尾款')) { // console.log('到手价包含预售字样'); return true; } else { // console.log('到手价不包含预售字样'); return false; } } } function check_isHavePreSale_havePreSale(iframeDocument) { const iframe = iframeDocument.querySelector('iframe'); var body = iframeDocument.body; if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument.body && !isNotIframeChineseName(iframeDocument.body.innerText)) { body = iframeDocument.body; } } // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; if (currentHTML.includes('预售')) { return true; } else { return false; } } function addContent_PreSale(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = currentHTML + `预售`; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } function addContent_PreSaleTime(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面预售时间信息 const updatedHTML = currentHTML + replacementMap.定金尾款时间; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } // 预设库存样式修改 const handlePreSetInventoryStyles = doc => { function check_content(content) { if (!numericRegex.test(content)) { if (content.includes('不可控')) { return false; } else { return true; } } else { return false; } } const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { if (check_content(pTag.textContent)) { pTag.textContent = '拉满'; const spanTags = pTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (check_content(spanTag.textContent)) { spanTag.textContent = '拉满'; } }); } }); }; // 现货库存样式修改 const handleInventoryStyles = doc => { let firstPTagFound = false; const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { // 获取 标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取 标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的 标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原 标签位置插入新的 标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的 标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();
标签 const ps = iframeDocument.querySelectorAll('p'); // 遍历每一个
标签 ps.forEach(p => { let innerHTML = p.innerHTML; let newInnerHTML = innerHTML; // 遍历 JSON 中的每个键值对 Object.keys(replacementMap).forEach(key => { const value = replacementMap[key]; // 使用正则表达式替换所有匹配的文本 const regex = new RegExp(key, 'g'); // 'g' 标志用于全局替换 newInnerHTML = newInnerHTML.replace(regex, value); }); // 将新的 HTML 内容赋给
标签 p.innerHTML = newInnerHTML; }); } const activateIframeAndModifyStyles = activateElementId => { const iframe = qyeryIframeForId(activateElementId); if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument) { // 清除原有的样式 if (sonMain_drawColor.getSwitchState()) { removeStyles(iframeDocument.body); } if (sonMain_calculator.getSwitchState()) { findAndCalculateExpressions(iframeDocument, calculate); } if (sonMain_smartReplace.getSwitchState()) { replaceTextContent(iframeDocument, replacementMap); } if (sonMain_drawColor.getSwitchState()) { // 第一行红色标记 if (activateElementId === 'livePrice') { if (sonMain_smartReplace.getSwitchState()) { autoWriteAvgPrice(iframeDocument); } modifyFirstPTagAndSpans(iframeDocument); applyPrefixSuffixColor(iframeDocument); } if (activateElementId === 'goodsName') { // 检查标题是否含有预售字样,检查是否是预售品 // console.log("check_isHavePreSale_in_goodName: " + check_isHavePreSale_in_goodName(iframeDocument)); // console.log("check_isHavePreSaleContent: " + check_isHavePreSaleContent()); if (!check_isHavePreSale_havePreSale(iframeDocument) && check_isHavePreSaleContent()) { addContent_PreSale(iframeDocument); } } // 规格首行非sku描述行蓝色 if (activateElementId === 'skuDesc') { skuDescFirstLineBlue(iframeDocument); } if (activateElementId === 'liveGameplay') { // 检查是否含有预售字样,检查是否是预售品 if (!check_isHavePreSaleContent(activateElementId) && check_isHavePreSaleContent()) { addContent_PreSaleTime(iframeDocument); } } // 获取对应的颜色映射 const colorMap = colorMaps[activateElementId]; const colorMap2 = colorMaps2[activateElementId]; const colorMap3 = colorMaps3[activateElementId]; if (colorMap) { if (activateElementId === 'inventory') { handleInventoryStyles(iframeDocument); } else if (activateElementId === 'preSetInventory') { handlePreSetInventoryStyles(iframeDocument); } else { if (colorMap) { addNewStyles(iframeDocument, colorMap); } if (colorMap2) { addNewStyles(iframeDocument, colorMap2); } if (colorMap3) { addNewStyles(iframeDocument, colorMap3); } } } else { console.error('未找到对应的颜色映射。'); } removeDataProcessed(iframeDocument); isMarioShow.push(activateElementId); } } else { console.error('无法访问iframe文档。'); } } else { console.error('未找到iframe元素。'); } }; const qyeryIframeForId = activateElementId => { const activateElement = document.querySelector(`#${activateElementId} .link-node-hover-text-container`); if (activateElement) { activateElement.click(); activateElement.focus(); return document.querySelector(`#${activateElementId} .tox-edit-area iframe`); } else { console.error('未找到激活元素。'); return null; } }; const removeDataProcessed = doc => { const replaceElements = doc.querySelectorAll('[data-replace="true"]'); replaceElements.forEach(element => { const parentElement = element.parentElement; if (parentElement.tagName.toLowerCase() === 'p') { // 检查
标签是否只有一个子元素,并且是当前的 const hasOnlySpanChild = parentElement.children.length === 1 && parentElement.children[0] === element; // 获取父 元素的纯文本内容(不包括子元素) const parentText = parentElement.textContent.trim(); if (hasOnlySpanChild && parentText === '无效内容') { // 如果 标签没有其他文本内容,移除整个 标签 parentElement.remove(); } else { // 否则,清空 的文本内容 element.textContent = ''; } } else { // 如果父元素不是 ,清空 的文本内容 element.textContent = ''; } }); }; // 规格首行非sku描述行蓝色 const skuDescFirstLineBlue = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.blue); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(spanTag, colorLibrary.blue); } }); } }; // 到手价数字行红色 const modifyFirstPTagAndSpans = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (numericRegex.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.red); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (numericRegex.test(spanTag.textContent)) { applyColor(spanTag, colorLibrary.red); } }); } }; function isNotIframeChineseName(text) { // 定义需要比较的内容数组 const contents = [ "商品标题", "规格信息", "直播价", "直播玩法/优惠方式", "预设库存", "现货库存" ]; // 遍历数组寻找匹配项 for (let i = 0; i < contents.length; i++) { if (text === contents[i]) { // 如果找到匹配项,返回其索引+1 return i + 1; } } // 如果没有找到匹配项,返回0 return 0; } // 检查到手价是否包含预售字样 function check_isHavePreSaleContent(idName = 'livePrice') { // const livePriceDoc = document.querySelector(`#livePrice`); var livePriceDoc = document.getElementById(idName); if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { livePriceDoc = body; } } const currentHTML = livePriceDoc.innerText; if (currentHTML.includes('定金') || currentHTML.includes('尾款')) { // console.log('到手价包含预售字样'); return true; } else { // console.log('到手价不包含预售字样'); return false; } } } function check_isHavePreSale_havePreSale(iframeDocument) { const iframe = iframeDocument.querySelector('iframe'); var body = iframeDocument.body; if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument.body && !isNotIframeChineseName(iframeDocument.body.innerText)) { body = iframeDocument.body; } } // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; if (currentHTML.includes('预售')) { return true; } else { return false; } } function addContent_PreSale(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = currentHTML + `预售`; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } function addContent_PreSaleTime(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面预售时间信息 const updatedHTML = currentHTML + replacementMap.定金尾款时间; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } // 预设库存样式修改 const handlePreSetInventoryStyles = doc => { function check_content(content) { if (!numericRegex.test(content)) { if (content.includes('不可控')) { return false; } else { return true; } } else { return false; } } const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { if (check_content(pTag.textContent)) { pTag.textContent = '拉满'; const spanTags = pTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (check_content(spanTag.textContent)) { spanTag.textContent = '拉满'; } }); } }); }; // 现货库存样式修改 const handleInventoryStyles = doc => { let firstPTagFound = false; const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { // 获取 标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取 标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的 标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原 标签位置插入新的 标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的 标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();
元素的纯文本内容(不包括子元素) const parentText = parentElement.textContent.trim(); if (hasOnlySpanChild && parentText === '无效内容') { // 如果
标签没有其他文本内容,移除整个
标签 parentElement.remove(); } else { // 否则,清空 的文本内容 element.textContent = ''; } } else { // 如果父元素不是 ,清空 的文本内容 element.textContent = ''; } }); }; // 规格首行非sku描述行蓝色 const skuDescFirstLineBlue = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.blue); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(spanTag, colorLibrary.blue); } }); } }; // 到手价数字行红色 const modifyFirstPTagAndSpans = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (numericRegex.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.red); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (numericRegex.test(spanTag.textContent)) { applyColor(spanTag, colorLibrary.red); } }); } }; function isNotIframeChineseName(text) { // 定义需要比较的内容数组 const contents = [ "商品标题", "规格信息", "直播价", "直播玩法/优惠方式", "预设库存", "现货库存" ]; // 遍历数组寻找匹配项 for (let i = 0; i < contents.length; i++) { if (text === contents[i]) { // 如果找到匹配项,返回其索引+1 return i + 1; } } // 如果没有找到匹配项,返回0 return 0; } // 检查到手价是否包含预售字样 function check_isHavePreSaleContent(idName = 'livePrice') { // const livePriceDoc = document.querySelector(`#livePrice`); var livePriceDoc = document.getElementById(idName); if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { livePriceDoc = body; } } const currentHTML = livePriceDoc.innerText; if (currentHTML.includes('定金') || currentHTML.includes('尾款')) { // console.log('到手价包含预售字样'); return true; } else { // console.log('到手价不包含预售字样'); return false; } } } function check_isHavePreSale_havePreSale(iframeDocument) { const iframe = iframeDocument.querySelector('iframe'); var body = iframeDocument.body; if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument.body && !isNotIframeChineseName(iframeDocument.body.innerText)) { body = iframeDocument.body; } } // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; if (currentHTML.includes('预售')) { return true; } else { return false; } } function addContent_PreSale(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = currentHTML + `预售`; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } function addContent_PreSaleTime(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面预售时间信息 const updatedHTML = currentHTML + replacementMap.定金尾款时间; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } // 预设库存样式修改 const handlePreSetInventoryStyles = doc => { function check_content(content) { if (!numericRegex.test(content)) { if (content.includes('不可控')) { return false; } else { return true; } } else { return false; } } const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { if (check_content(pTag.textContent)) { pTag.textContent = '拉满'; const spanTags = pTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (check_content(spanTag.textContent)) { spanTag.textContent = '拉满'; } }); } }); }; // 现货库存样式修改 const handleInventoryStyles = doc => { let firstPTagFound = false; const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { // 获取 标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取 标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的 标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原 标签位置插入新的 标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的 标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();
,清空 的文本内容 element.textContent = ''; } }); }; // 规格首行非sku描述行蓝色 const skuDescFirstLineBlue = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.blue); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (!check_skuDescFirstLine.test(firstPTag.textContent)) { applyColor(spanTag, colorLibrary.blue); } }); } }; // 到手价数字行红色 const modifyFirstPTagAndSpans = doc => { const firstPTag = doc.querySelector('#tinymce p'); if (firstPTag) { if (numericRegex.test(firstPTag.textContent)) { applyColor(firstPTag, colorLibrary.red); } const spanTags = firstPTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (numericRegex.test(spanTag.textContent)) { applyColor(spanTag, colorLibrary.red); } }); } }; function isNotIframeChineseName(text) { // 定义需要比较的内容数组 const contents = [ "商品标题", "规格信息", "直播价", "直播玩法/优惠方式", "预设库存", "现货库存" ]; // 遍历数组寻找匹配项 for (let i = 0; i < contents.length; i++) { if (text === contents[i]) { // 如果找到匹配项,返回其索引+1 return i + 1; } } // 如果没有找到匹配项,返回0 return 0; } // 检查到手价是否包含预售字样 function check_isHavePreSaleContent(idName = 'livePrice') { // const livePriceDoc = document.querySelector(`#livePrice`); var livePriceDoc = document.getElementById(idName); if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { livePriceDoc = body; } } const currentHTML = livePriceDoc.innerText; if (currentHTML.includes('定金') || currentHTML.includes('尾款')) { // console.log('到手价包含预售字样'); return true; } else { // console.log('到手价不包含预售字样'); return false; } } } function check_isHavePreSale_havePreSale(iframeDocument) { const iframe = iframeDocument.querySelector('iframe'); var body = iframeDocument.body; if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument.body && !isNotIframeChineseName(iframeDocument.body.innerText)) { body = iframeDocument.body; } } // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; if (currentHTML.includes('预售')) { return true; } else { return false; } } function addContent_PreSale(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = currentHTML + `预售`; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } function addContent_PreSaleTime(iframeDocument) { // 获取 元素 const body = iframeDocument.body; // 获取当前的 body 内的 HTML 内容 const currentHTML = body.innerHTML; // 在当前 HTML 内容后面预售时间信息 const updatedHTML = currentHTML + replacementMap.定金尾款时间; // 将更新后的 HTML 设置回 body 内 body.innerHTML = updatedHTML; } // 预设库存样式修改 const handlePreSetInventoryStyles = doc => { function check_content(content) { if (!numericRegex.test(content)) { if (content.includes('不可控')) { return false; } else { return true; } } else { return false; } } const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { if (check_content(pTag.textContent)) { pTag.textContent = '拉满'; const spanTags = pTag.querySelectorAll('span'); spanTags.forEach(spanTag => { if (check_content(spanTag.textContent)) { spanTag.textContent = '拉满'; } }); } }); }; // 现货库存样式修改 const handleInventoryStyles = doc => { let firstPTagFound = false; const pTags = doc.querySelectorAll('#tinymce p'); pTags.forEach(pTag => { // 获取 标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取 标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的 标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原 标签位置插入新的 标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的 标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();
标签内的所有文本内容,并将连续的 转换为 let content = pTag.innerHTML.replace(/( )+/g, '无效内容'); // 获取
标签内的所有文本内容,并按 标签分割成数组 const segments = content.split(''); // 处理每个分割后的段落 segments.forEach((segment, index) => { // 创建临时容器元素以便于操作 HTML 字符串 const tempContainer = document.createElement('div'); tempContainer.innerHTML = segment; // 获取段落的纯文本内容 const segmentText = tempContainer.textContent; if (!firstPTagFound && segmentText.includes('预售')) { firstPTagFound = true; } // 创建新的
标签用于包裹分隔后的段落 const newPTag = document.createElement('p'); newPTag.innerHTML = segment; if (numericRegex.test(segmentText) || segmentText.includes('--')) { applyColor(newPTag, colorMaps.inventory.default); } else { if (firstPTagFound) { applyColor(newPTag, colorMaps.inventory.color2); } else { applyColor(newPTag, colorMaps.inventory.color1); } } // 在原
标签位置插入新的
标签 pTag.parentNode.insertBefore(newPTag, pTag); }); // 移除原始的
标签 pTag.parentNode.removeChild(pTag); }); }; function autoWriteAvgPrice(iframeDocument) { const ps = iframeDocument.querySelectorAll('p'); // 更新检测输入格式的正则表达式,支持"?40/" 或 "?30/"以及"//"结构 const pattern = /^(.*(:|:))?\d+(\.\d+)?(((\?|\?)\d+\/?)?(\/?\/\d+(\.\d+)?[^\d/\*]+)?)?(\/\d+(\.\d+)?[^\d/\*]+|[*]\d+(\.\d+)?[^\d/\*]+)*$/; ps.forEach(p => { if (p.querySelector('span')) { // 情况 1: p 标签内有 span 标签,需要处理 span 内的文本 processSpans(p, pattern); } else { // 情况 2: 只有 p 标签,没有嵌套的 span 标签 let newInnerHTML = ''; // 分割HTML内容 const parts = p.innerHTML.split(/(| )/); parts.forEach(part => { let styledPart = part; // 检查part是否符合格式 if (pattern.test(part)) { // 调用parseInput来解析part并生成新内容 const { prefix, price, rex, num, units } = parseInput(part); const newContent = generateOutput(prefix, price, rex, num, units); styledPart = newContent; // 将解析后的结果替换原内容 } newInnerHTML += styledPart; // 拼接处理后的部分 }); // 更新p元素的内容 p.innerHTML = newInnerHTML; } }); function processSpans(element, pattern) { const spans = element.querySelectorAll('span'); spans.forEach(span => { const textContent = span.textContent; // 检查textContent是否符合格式 if (pattern.test(textContent)) { // 调用parseInput来解析textContent并生成新内容 const { prefix, price, rex, num, units } = parseInput(textContent); const newContent = generateOutput(prefix, price, rex, num, units); // 更新span的内容 span.innerHTML = newContent; } }); } } // 定义 parseInput 函数,用于解析输入 function parseInput(input) { // 更新正则表达式,先匹配价格和特殊的 "?40/" 或 "?30/" 结构,后面匹配 "/*" 的单位结构 const prefixPatt = /^.*(:|:)/; // 匹配前缀内容 const cleanedInput = input.replace(prefixPatt, '').trim(); // 移除匹配了的前缀内容 const pricePattern = /\d+(\.\d+)?/; // 匹配开头的价格 const questionPattern = /(\?|\?)\d+\/?/; // 匹配 "?40/" 或 "?30/" 结构 const unitPattern = /(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/g; // 匹配剩下的部分 // 捕获开头的价格 const priceMatch = cleanedInput.match(pricePattern); const price = priceMatch ? parseFloat(priceMatch[0]) : null; // 捕获前缀内容 const prefixMatch = input.match(prefixPatt); const prefix = prefixMatch ? prefixMatch[0].slice(0, -1) : ''; let rex = []; let num = []; let units = []; // 匹配 "?40/" 或 "?30/" 结构,并存入 rex const specialMatch = cleanedInput.match(questionPattern); if (specialMatch) { rex.push(specialMatch[0]); // 完整存储 "?40/" 或 "?30/" } // 匹配剩下的部分:形如 "/* 数字 单位" const matches = cleanedInput.match(unitPattern); if (matches) { matches.forEach((match, index) => { const [, symbol, number, , unit] = match.match(/(\/\/?|[*])(\d+(\.\d+)?)([^\d/\*]+)/); rex.push(symbol); let quantity = parseFloat(number); if (symbol === "*" && index > 0) { quantity *= num[num.length - 1]; } num.push(quantity); units.push(unit.trim()); }); } return { prefix, price, rex, num, units }; } // 定义 generateOutput 函数,用于生成输出内容 function generateOutput(prefix, price, rex, num, units) { let prefixContent = ''; if (prefix !== '') { prefixContent = `${prefix}:`; } if (rex.length === 0) { return prefixContent + price; } const fristRex = rex[0]; let output = `到手共${num[0]}${units[0]}`; // 判断第一个 rex 是否是 "/" if (fristRex != "/") { // 如果 fristRex 是 "//",处理特定逻辑 if (fristRex == '//') { prefixText = prefixContent; priceText = `${price}`; output = prefixText + priceText + "" + output; } // 使用正则表达式判断 fristRex 是否为 "?40/" 或 "?30/" 类似结构 const questionPattern = /^(\?|\?)\d+\/?$/; // 使用正则表达式直接提取数字部分,默认返回"0" const depositPrice = parseFloat(fristRex.match(/^(\?|\?)(\d+)\/?$/)?.[2] || "0"); // 如果 fristRex 匹配 "?40/" 或 "?30/" 结构,生成定金和尾款部分 if (questionPattern.test(fristRex)) { const finalPayment = (price - depositPrice).toFixed(2).replace(/\.?0+$/, ""); // 计算尾款,并保留两位小数 if (rex.length > 1) { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + "" + output; } else { prefixText = prefixContent; priceText = `${price}`; finalPalnText = `定金${depositPrice}+尾款${finalPayment}`; output = prefixText + priceText + "" + finalPalnText + ""; return output; } } } // 处理每个单位的平均价格 for (let i = 0; i < num.length; i++) { let avgPrice = (price / num[i]).toFixed(2).replace(/\.?0+$/, ""); // 计算结果并去掉末尾多余的零 output += `平均每${units[i]}${avgPrice}`; if (i < num.length - 1) { output += `到手共${num[i + 1]}${units[i + 1]}`; } } // 去除末尾多余的 output = output.replace(/$/, ""); return output; } function buttonClickForAddPreSaleTime() { var iframe; // 用于存储临时的 iframe iframe = qyeryIframeForId('goodsName'); var iframeDocument_goodsName = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_goodsName && !check_isHavePreSale_havePreSale(iframeDocument_goodsName)) { addContent_PreSale(iframeDocument_goodsName); } iframe = qyeryIframeForId('liveGameplay'); var iframeDocument_liveGameplay = iframe.contentDocument || iframe.contentWindow.document; if (iframeDocument_liveGameplay && !check_isHavePreSaleContent('liveGameplay')) { addContent_PreSaleTime(iframeDocument_liveGameplay); } } /* 计算器功能区 */ const calculate = [ { regex: /折扣力度.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, replaceFunc: (text, result) => { // 替换文本中的折扣内容 let updatedText = text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `${result}`); // 确保结果前有一个“约”字,并且前面有“:”或“:” if (!updatedText.includes('约')) { // 检查是否已有“:”或“:”,防止重复添加 if (!updatedText.includes(':') && !updatedText.includes(':')) { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1:约${result}`); } else { updatedText = updatedText.replace(/(折扣力度.*?)(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `$1约${result}`); } } else { updatedText = updatedText.replace(/(:约|:约)?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))/, `:约${result}`); } // 确保结果后面有一个“折”字 if (!updatedText.endsWith('折')) { updatedText += '折'; } return updatedText; }, decimalPlaces: 1, multiplier: 10, trimTrailingZeros: true }, { regex: /.*?(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, replaceFunc: (text, result) => text.replace(/(\d+[\d+\-*/().]*\d*|\([\d+\-*/().]+\))==/, `${result}`), decimalPlaces: 2, multiplier: 1, trimTrailingZeros: true }, ]; // 计算表达式的函数 const calculateExpression = (expression, decimalPlaces = 2, multiplier = 1, trimTrailingZeros = false) => { try { let result = eval(expression); // 注意:eval() 存在安全性问题,确保传入的表达式是安全的。 result = result * multiplier; // 放大结果 let formattedResult = result.toFixed(decimalPlaces); // 保留指定的小数位数 // 根据参数决定是否去除末尾的零 if (trimTrailingZeros) { formattedResult = parseFloat(formattedResult).toString(); } return formattedResult; } catch (e) { console.error('表达式计算错误:', e); return expression; // 如果计算错误,返回原表达式 } }; const findAndCalculateExpressions = (iframeDocument, calculate) => { const discountElements = iframeDocument.querySelectorAll('p, span'); discountElements.forEach(element => { let text = element.textContent.replace(/。/g, '.'); // 替换所有中文小数点为英文小数点 text = text.replace(/(/g, '(').replace(/)/g, ')'); // 替换中文括号为英文括号 calculate.forEach(({ regex, replaceFunc, decimalPlaces, multiplier, trimTrailingZeros }) => { const match = text.match(regex); // console.log(match); if (match) { const expression = match[1]; // 检查是否为“折扣力度”的正则表达式 if (regex.source.includes('折扣力度')) { if (/[+\-*/()]/.test(expression)) { // 如果表达式包含运算符,进行计算 const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } else { // 如果表达式不包含运算符,直接使用替换函数处理 text = replaceFunc(text, expression); } } else { // 其他情况照常处理 // 检查表达式是否包含运算符 if (/[+\-*/()]/.test(expression)) { const result = calculateExpression(expression, decimalPlaces, multiplier, trimTrailingZeros); text = replaceFunc(text, result); } } element.textContent = text; } }); }); }; // 新增控制功能 // 支持单个元素内容的着色 // 封装函数返回包含SVG图标的div function createMarioSVGIconWrapper(id, isClick = true, size = 14) { // 创建一个 div 容器 var divWrapper = document.createElement('div'); divWrapper.className = 'svg-icon-wrapper'; // 添加一个类名,便于查找和样式控制 divWrapper.style.cssText = 'align-items: center; cursor: pointer; display: none;'; // 样式控制'; // 设置 div 的 id if (id) { divWrapper.id = id; } // 创建 SVG 图标 var svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute('class', 'icon custom-mario-svg'); // 添加自定义类名 svgIcon.setAttribute('viewBox', '0 0 1024 1024'); svgIcon.setAttribute('width', size); svgIcon.setAttribute('height', size); svgIcon.innerHTML = ''; svgIcon.style.cssText = 'vertical-align: middle;'; // 垂直居中样式 // 将 SVG 添加到 div 容器中 divWrapper.appendChild(svgIcon); if (isClick) { // 根据 id 绑定点击事件 divWrapper.addEventListener('click', function () { // 提取 id 中的 suffix 部分 var idSuffix = id.replace('svg-icon-', ''); // 根据 id 调用对应的函数 switch (id) { case 'svg-icon-goodsName': activateIframeAndModifyStyles('goodsName'); showNotification("着色成功", 5000); break; case 'svg-icon-skuDesc': activateIframeAndModifyStyles('skuDesc'); showNotification("着色成功", 5000); break; case 'svg-icon-livePrice': activateIframeAndModifyStyles('livePrice'); // 判断“预售信息”,辅助自动输入定金尾款时间 if (check_isHavePreSaleContent()) { buttonClickForAddPreSaleTime(); } showNotification("着色成功", 5000); break; case 'svg-icon-liveGameplay': activateIframeAndModifyStyles('liveGameplay'); showNotification("着色成功", 5000); break; case 'svg-icon-preSetInventory': activateIframeAndModifyStyles('preSetInventory'); showNotification("着色成功", 5000); break; case 'svg-icon-inventory': activateIframeAndModifyStyles('inventory'); showNotification("着色成功", 5000); break; default: break; } // 仅当 idSuffix 不在数组中时才添加 if (!isMarioShow.includes(idSuffix)) { isMarioShow.push(idSuffix); } }); } else { divWrapper.style.display = 'flex'; divWrapper.className = 'svg-icon-wrapper-no-data'; } return divWrapper; } // 查找表格中的目标单元格并添加SVG图标 function addSVGToSpecificTDs() { // 获取 class="card-content-container" 内的表格 var container = document.querySelector('.card-content-container'); if (!container) return; var table = container.querySelector('table'); if (!table) return; var tbody = table.querySelector('tbody'); if (!tbody) return; // 获取 tbody 内的第二个 tr var secondTR = tbody.querySelectorAll('tr')[1]; // 获取第二个 tr if (!secondTR) return; // 获取第二个 tr 中的所有 td var tds = secondTR.querySelectorAll('td'); var idMap = { "商品名": "svg-icon-goodsName", "规格": "svg-icon-skuDesc", "直播间到手价": "svg-icon-livePrice", "优惠方式": "svg-icon-liveGameplay", "预设库存": "svg-icon-preSetInventory", "现货库存": "svg-icon-inventory" }; // 文本内容与ID的映射 tds.forEach(function (td) { // 检查 td 的文本内容是否在目标文本内容列表中 var span = td.querySelector('.link-node-container > span'); if (span && idMap.hasOwnProperty(span.textContent.trim())) { // 检查是否已经存在封装的 SVG 图标,避免重复添加 if (!td.querySelector('.svg-icon-wrapper')) { // 获取对应的 id var id = idMap[span.textContent.trim()]; // 创建包含 SVG 图标的 div 容器并设置 id var svgWrapper = createMarioSVGIconWrapper(id); // 将 SVG 容器插入到 span 之后 span.parentNode.insertAdjacentElement('afterend', svgWrapper); } } }); } // 初始化存储被点击事件的数组 var isMarioShow = []; // 函数:控制每个 divWrapper 的显示状态 function updateDivWrapperDisplay(isShow) { // 获取所有 class 为 'svg-icon-wrapper' 的元素 const divWrappers = document.querySelectorAll('.svg-icon-wrapper'); // 遍历所有 divWrapper for (const divWrapper of divWrappers) { if (isShow) { divWrapper.style.display = 'flex'; } else { // 获取该 divWrapper 的 id var wrapperId = divWrapper.id; // 检查是否存在于 isMarioShow 数组中 if (isMarioShow.includes(wrapperId.replace('svg-icon-', ''))) { divWrapper.style.display = 'flex'; } else { divWrapper.style.display = 'none'; } } } } /* 淘宝、天猫主图复制到剪贴板功能 */ function createGetTmallPngButton() { // 找到匹配的元素的编号 function findMatchingIndex(wrapperClass, imgClass) { for (let i = 0; i < wrapperClass.length; i++) { const wrapper = document.querySelector(wrapperClass[i]); if (wrapper) { const img = wrapper.querySelector(imgClass[i]); if (img) { return i; // 返回匹配的编号 } } } return -1; // 如果没有找到匹配的元素,则返回 -1 } if (!document.querySelector('#button_getTmallPngButton')) { const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2']; const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img']; const matchingIndex = findMatchingIndex(wrapperClass, imgClass); if (matchingIndex !== -1) { const wrapper = document.querySelector(wrapperClass[matchingIndex]); // console.log("wrapper:", wrapper); if (wrapper) { const button = document.createElement('button'); button.textContent = '复制图片'; button.id = 'button_getTmallPngButton'; button.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 5px 20px; font-size: 16px; background-color: rgba(0, 0, 0, 0.5); color: #fff; border: none; border-radius: 999px; font-family: AlibabaPuHuiTi_2_55_Regular; backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */ -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */ text-align: center; /* 文本居中显示 */ cursor: pointer; opacity: 0; transition: opacity 0.3s ease; z-index: 999; `; // 控制按钮显示 wrapper.addEventListener('mouseenter', () => { button.style.opacity = '1'; }); // 控制按钮隐藏 wrapper.addEventListener('mouseleave', () => { button.style.opacity = '0'; }); button.addEventListener('click', async () => { const img = wrapper.querySelector(imgClass[matchingIndex]); // console.log("img:", img); if (img) { try { const imageUrl = img.src; const response = await fetch(imageUrl); const blob = await response.blob(); const image = new Image(); image.src = URL.createObjectURL(blob); image.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); canvas.toBlob(async (blob) => { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); showNotification("图片已成功复制到剪贴板", undefined, true); // alert('Image copied to clipboard!'); } catch (error) { console.error('Failed to copy image to clipboard:', error); // alert('Failed to copy image to clipboard.'); showNotification('图片复制失败!'); } }, 'image/png'); }; } catch (error) { showNotification('图片复制失败!'); console.error('Failed to fetch or process image:', error); // alert('Failed to copy image to clipboard.'); } } else { // alert('Image not found!'); } }); wrapper.style.position = 'relative'; // 确保按钮在图片上层 wrapper.appendChild(button); } else { // console.error('Wrapper element not found.'); } } else { // console.error('No matching element found.'); } } else { // console.log('Button already exists, skipping creation.'); } } /* 用于主播排序的辅助函数 */ // 封装处理和生成多维数组数据的函数 function generateJsonFromText(textInput) { // 简写与全称的对应表 const shorthandToFull = { "野": "小野", "月": "小月", "霸": "小霸王", "迎": "小迎", "京": "京京", "祖": "阿祖", "凯": "凯子", "锅": "小锅", }; // 处理文本,去除“+”及其后的内容 // const processText = (text) => text.split('+')[0].trim(); const processText = (text) => { let result = ''; // 首先移除“+”及其后的内容 const firstPart = text.split('+')[0]; // 遍历firstPart的每个字符 for (let char of firstPart) { // 如果字符是shorthandToFull的键,则添加到结果中 if (shorthandToFull.hasOwnProperty(char)) { result += char; } } // 返回处理后的文本 return result.trim(); }; // 将简写转换为全称 const getFullNames = (text) => { return text.split('').map(ch => shorthandToFull[ch] || ch); }; // 生成多维数组格式数据 const result = []; // 示例输入 [1][0][0] // 解释:第一个位置存储多个主播的排列组合,第二个位置0存储主播名字,其余位置存储其rowIndex值,第三个位置用于读取主讲或副讲 // 获取主讲 resultArray[2][0][0]; // 获取副讲 resultArray[2][0][1]; // 获取其列 resultArray[2][i] const texts = textInput.trim().split('\n'); texts.forEach((text, index) => { const processedText = processText(text); const fullNamesArray = getFullNames(processedText); // 查找是否已存在相同的 fullNamesArray const existingEntry = result.find(entry => { return JSON.stringify(entry[0]) === JSON.stringify(fullNamesArray); }); if (existingEntry) { // 如果存在相同的组合,追加索引 existingEntry.push(index); } else { // 如果不存在,创建一个新的组合 result.push([fullNamesArray, index]); } }); return result; } // 页面控制函数 function itemPageScroll(height, addScroll = true) { return new Promise((resolve) => { // .rc-virtual-list-holder const viewport = document.querySelector('.ag-body-vertical-scroll-viewport'); // 获取页面滚动区域 if (addScroll) { if (height != 0) { viewport.scrollTop += height; // 向下滚动一屏 } else { viewport.scrollTop = 0; // 回到顶部 } } else { viewport.scrollTop = height; // 直接滚动到指定位置 } console.log('scrolling to', viewport.scrollTop); // 打印当前滚动高度 // 通过 setTimeout 来模拟等待页面加载完成 setTimeout(() => { resolve(); // 滚动完成后继续执行 }, 800); // 延迟时间可以根据实际加载时间调整 }); } // 商品选择函数 function selectItemForRowIndex(rowIndex, retries = 5, delay = 500) { return new Promise((resolve, reject) => { // 找到带有指定 row-index 的 div const targetDiv = document.querySelector(`div[row-index="${rowIndex}"]`); // 在 targetDiv 内查找 col-id="choice" 的元素 if (targetDiv) { const choiceElement = targetDiv.querySelector('div[col-id="choice"]'); // 在 choiceElement 内找到唯一的 input 元素 if (choiceElement) { const inputElement = choiceElement.querySelector('input'); if (inputElement) { inputElement.click(); // 点击 input 元素 // console.log(`Selected item for row-index="${rowIndex}"`); resolve(); // 选择完成后继续执行 } else { // input 元素未找到的情况 retryOrReject(`未找到 input 元素在 col-id="choice" 的元素内`); } } else { // console.log(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); retryOrReject(`未找到 col-id="choice" 的元素在 row-index="${rowIndex}" 的 div 内`); } } else { // console.log(`未找到 row-index="${rowIndex}" 的 div`); retryOrReject(`未找到 row-index="${rowIndex}" 的 div`); } function retryOrReject(errorMessage) { if (retries > 0) { setTimeout(() => { // 递归调用自己,并减少重试次数 selectItemForRowIndex(rowIndex, retries - 1, delay).then(resolve).catch(reject); }, delay); } else { reject(errorMessage); // 达到最大重试次数后,返回错误 } } }); } // 模拟鼠标点击,激活主播选择框 // 示例调用:点击 "主讲主播" 的选择框 // clickSelectByTitle("主讲主播"); // 示例调用:点击 "副讲主播" 的选择框 // clickSelectByTitle("副讲主播"); async function clickSelectByTitle(title, retries = 5, delay = 500) { // 重试机制,最多重试 `retries` 次,每次等待 `delay` 毫秒 for (let i = 0; i < retries; i++) { const labelElement = document.querySelector(`label[title="${title}"]`); if (labelElement) { const parentElement = labelElement.parentElement; if (parentElement && parentElement.parentElement) { const selectSelector = parentElement.parentElement.querySelector('.ant-select-selector'); if (selectSelector) { // 模拟点击 const clickEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); selectSelector.dispatchEvent(clickEvent); // 模拟点击事件 // console.log(`已激活并点击 ${title} 对应的选择框`); return; // 成功找到并点击后直接返回 } else { // console.log(`未找到 ${title} 对应的 .ant-select-selector 元素`); } } else { // console.log(`无法获取到 ${title} 的父元素`); } } else { // console.log(`未找到 title 为 "${title}" 的 label 元素`); } // 如果没找到,等待一段时间再重试 if (i < retries - 1) { // console.log(`重试 ${i + 1}/${retries} 次后等待 ${delay} 毫秒...`); await new Promise(resolve => setTimeout(resolve, delay)); } } // 如果所有重试都失败了,抛出一个错误 throw new Error(`无法找到 title 为 "${title}" 的元素。`); } // 选择指定的主播 function selectAnchor(anchor, primary = true) { return new Promise((resolve, reject) => { // 根据 primary 参数决定目标容器的选择器 const targetDiv = primary ? '#primaryAnchor_list' : '#assistantAnchor_list'; // 获取视窗元素 const viewFather = document.querySelector(targetDiv).parentElement; const viewport = viewFather.querySelector('.rc-virtual-list-holder'); // 定义一个异步函数来执行循环操作 (async function trySelect() { for (let attempt = 0; attempt < 4; attempt++) { // 查找目标选项 const targetOption = `.ant-select-item[title="${anchor}"]`; const optionElement = document.querySelector(targetDiv).parentElement.querySelector(targetOption); if (optionElement) { // 如果找到选项,则点击并完成操作 optionElement.click(); // console.log(`已选择 ${anchor} 主播`); resolve(); return; // 结束函数 } // 如果没有找到,滚动视窗 viewport.scrollTop += 256; // console.log(`未找到 ${anchor} 主播,正在尝试第 ${attempt + 1} 次滚动`); // 等待一点时间以让页面加载新内容 await new Promise(r => setTimeout(r, 500)); } // 如果经过多次尝试仍未找到,抛出错误或处理异常 // console.log(`未能找到 ${anchor} 主播,已尝试 ${4} 次`); reject(new Error(`未能找到 ${anchor} 主播`)); })(); }); } // 点击“计划主播排班” function clickScheduleButton() { return new Promise((resolve) => { const scheduleButtonClassName = '.ant-btn.css-9fw9up.ant-btn-default.primaryButton___N3z1x' clickButton(true, 0, document, scheduleButtonClassName, '计划主播排班'); resolve(); }); } // 点击“排班页面”的“确定”按钮 function clickConformButtonForSchedule() { return new Promise((resolve) => { const scheduleClassName = '.ant-modal-content'; const conformButtonClassName = '.ant-btn.css-9fw9up.ant-btn-primary'; clickButton(true, 0, scheduleClassName, conformButtonClassName); resolve(); }); } async function processItems() { // 返回输入文本中对应的商品个数 function countAllItemNum(resultArray) { var countNum = 0; for (var i = 0; i < resultArray.length; i++) { countNum += resultArray[i].length - 1; } // console.log('countNum:', countNum); return countNum; } // 返回当前已经排序的最大商品序号 function getMaxForScheduledItemIndex() { if (scheduledItems.length === 0) { return 0; } // 对已排班的商品序号进行排序 scheduledItems.sort((a, b) => a - b); // 遍历数组,找到最小的缺失序号 for (let i = 0; i < scheduledItems.length; i++) { if (scheduledItems[i] !== i) { // console.log('Missing index:', i); return i; // 一旦发现某个序号不连续,返回这个序号 } } // 如果所有序号都连续,则返回下一个未使用的序号 return scheduledItems.length; } let scheduledItems = []; // 已排班的商品序号 let rounder = 0; // 轮次计数器 let itemBlockHeight = 100; try { const elements = document.getElementsByClassName('fontLinkd-[#333_20_20_Bold]'); const textContent = elements[0].innerText || elements[0].textContent; const countItem = parseInt(textContent, 10); // link上的商品总数 // 原生浏览器弹窗提示 // const textInput = prompt('请输入主播排班表格内容,一行对应一个商品'); const textInput = await createDropdownModal(dropdownContainer, '主播排序'); const resultArray = generateJsonFromText(textInput); // console.log(resultArray); // 商品数检查 if (countAllItemNum(resultArray) > countItem) { click_itemSort_anchorSort(); setTimeout(() => { showNotification('输入了过多的主播,请检查后重新输入!'); }, 1500); return; } while (rounder < resultArray.length) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 await itemPageScroll(itemBlockHeight * getMaxForScheduledItemIndex(), false); // 回到合适的位置或许是顶部 showNotification('正在处理第 ' + (rounder + 1) + '/' + resultArray.length + ' 轮次 ' + loadImageIcon(), 0); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 let index = 1; let checkCount = 0; for (let i = getMaxForScheduledItemIndex(); i < countItem; i++, checkCount++) { if (!itemSort_anchorSort.getDivButtonState()) return; // 跳出循环,如果主播排序未打开,则不执行任何操作 if (resultArray[rounder][index] == i) { await selectItemForRowIndex(i); // 选择指定的行 scheduledItems.push(i); // 记录已排班的商品序号 index++; } if ((checkCount + 1) % 4 === 0) await itemPageScroll(itemBlockHeight * 4); // 每处理4行,滚动页面 // console.log('Index:', index, 'Length:', resultArray[rounder].length); if (index === resultArray[rounder].length) { // console.log('Executing scheduling...'); await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 await clickScheduleButton(); await clickSelectByTitle("主讲主播"); await selectAnchor(resultArray[rounder][0][0], true); await clickSelectByTitle("副讲主播"); if (resultArray[rounder][0][1]) { await selectAnchor(resultArray[rounder][0][1], false); } await clickConformButtonForSchedule(); rounder++; break; // 跳出循环,继续处理下一个商品 } } // 确保整个循环内容都执行完再进入下一次迭代 await new Promise(resolve => setTimeout(resolve, 500)); // 等待500毫秒,可根据需要调整 } setTimeout(() => { click_itemSort_anchorSort(); }, 1500); showNotification('全部处理完成!'); } catch (error) { // 捕获任何异常,并显示错误通知 click_itemSort_anchorSort(); setTimeout(() => { showNotification('处理过程中出现错误!'); }, 1500); console.error('Error occurred:', error); } finally { // 可选的: 在所有操作完成后执行清理工作 // console.log('处理流程结束'); } } function click_itemSort_anchorSort() { document.getElementById('itemSort_anchorSort_divButton').click(); } // 输入弹窗 function createDropdownModal(dropdownContainer, titleText) { return new Promise((resolve, reject) => { // 检查是否已有弹窗存在,如果有则移除 const existingModal = dropdownContainer.querySelector('.dropdown-modal'); if (existingModal) { dropdownContainer.removeChild(existingModal); } dropdownButton.style.display = 'none'; // 隐藏按钮 // 创建弹窗容器 var dropdownDivModal = document.createElement('div'); dropdownDivModal.classList.add('dropdown-modal'); // 添加一个类以便于识别 dropdownDivModal.style.cssText = ` position: absolute; top: 0; margin: 14px; width: 172px; height: 108px; background-color: rgba(233, 233, 233, 0.7); backdrop-filter: blur(8px) brightness(90%); border: 1px solid rgba(255, 98, 0, 0.25); border-radius: 10px; box-sizing: border-box; display: flex; flex-direction: column; z-index: 3; transform-origin: top center; `; // 创建标题行 const title = document.createElement('div'); title.textContent = titleText || '弹窗标题'; title.style.cssText = ` padding: 8px 10px; font-size: 14px; font-weight: bold; color: rgb(51, 51, 51); border-bottom: 0px solid #ccc; text-align: left; flex-shrink: 0; `; dropdownDivModal.appendChild(title); // 创建富文本框 const textarea = document.createElement('textarea'); textarea.style.cssText = ` width: calc(100% - 20px); background-color: rgba(249, 249, 249, 0.7); height: 30px; margin: 0px 10px; padding: 5px; font-size: 12px; border: 0px solid #ccc; border-radius: 4px; resize: none; line-height: 1.2; box-sizing: border-box; flex-grow: 1; `; dropdownDivModal.appendChild(textarea); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; justify-content: space-between; padding: 8px 10px; border-top: 0px solid #ccc; flex-shrink: 0; `; // 创建“取消”按钮 const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #f44336; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; cancelButton.onclick = () => { dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 reject('用户取消了输入'); }; buttonContainer.appendChild(cancelButton); // 创建“确认”按钮 const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 3px 8px; font-size: 12px; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; flex-basis: 48%; `; confirmButton.onclick = () => { const textInput = textarea.value; dropdownContainer.removeChild(dropdownDivModal); dropdownButton.style.display = ''; // 恢复按钮 resolve(textInput); // 在确认时返回输入的内容 }; buttonContainer.appendChild(confirmButton); dropdownDivModal.appendChild(buttonContainer); dropdownContainer.appendChild(dropdownDivModal); }); } function findGoodsByShopName(shopName = clipboardHistory, goodsDict = goodsUrlCheckArray) { // console.log('findGoodsByShopName:', shopName); // debug let result = ["辛苦确定下以下挂链链接是否需要更换!\n"]; var date = GM_getValue('titlePrint_extractedDate', '') var i = 1; for (const upLiveId in goodsDict) { const goodsInfo = goodsDict[upLiveId]; if (goodsInfo.shopName === shopName) { result.push(`${i} \[${isLinkTrue(goodsInfo.weight)}\]-${filterBadGoodName(goodsInfo.sessionGoodsName)}:${goodsInfo.liveLink}`); i++; } } if (i === 1) { // 没有找到对应店铺 result = []; showNotification('未找到对应店铺,请检查剪切板是否正确!'); return result; } result.push(`\n【${shopName}】挂链日期:${date}`); // console.log('findGoodsByShopName:', result.join('\n')); // debug showNotification(`${shopName}的挂链链接已复制到剪切板`); GM_setClipboard(result.join('\n')); return result; function isLinkTrue(weight) { if (weight === 0) { return '未确认'; } else { return '已确认'; } } function filterBadGoodName(goodsName) { // 使用正则表达式去除换行符和“预售”信息 const cleanedName = goodsName.replace(/(\r\n|\n|\r|预售)/g, ''); return cleanedName.trim(); // 去除首尾的空白字符 } } function addDefaultButtonForHomePage() { var buttonName = '完整复制'; var buttonId = 'allCopyButton'; const smartCopyButton = createDefaultButton(buttonId, buttonName, () => { findGoodsByShopName(); }); // 找到搜索栏目 const searchToolBar = document.querySelector('.ant-pro-table-list-toolbar-left'); const scButton = document.getElementById(buttonId); if (searchToolBar) { if (!scButton) { searchToolBar.appendChild(smartCopyButton); } if (checkActiveTab_GuaLian() && sonMain_linkAllCopySwitch.getSwitchState()) { scButton.style.display = ''; } else { scButton.style.display = 'none'; } } } function createDefaultButton(id = 'testButton', text = '测试按钮', clickHandler) { // 创建按钮元素 const button = document.createElement('button'); button.type = 'button'; button.id = id; button.className = 'ant-btn css-9fw9up ant-btn-default'; // 创建按钮内部的文本元素 const span = document.createElement('span'); span.textContent = text; button.appendChild(span); // 绑定点击事件处理函数 if (typeof clickHandler === 'function') { button.addEventListener('click', clickHandler); } return button; } /* 补贴生成 */ function createSubsidyTextButton(isActivated = sonMain_subsidyText.getSwitchState()) { if (!isActivated) { return; } const targetClass = '[class*="ant-space"][class*="css-9fw9up"][class*="ant-space-horizontal"][class*="ant-space-align-center"]'; const subsidyText_observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const targetElement = document.querySelector(targetClass); if (targetElement) { if (document.querySelector('.subsidyText')) { subsidyText_observer.disconnect(); return; } var subsidyText = document.createElement('div'); subsidyText.classList.add('ant-space-item'); subsidyText.style.display = 'none'; var subsidyTextButton = document.createElement('button'); subsidyTextButton.textContent = '补贴生成'; subsidyTextButton.classList.add('ant-btn', 'css-9fw9up', 'ant-btn-default', 'subsidyText'); subsidyText.appendChild(subsidyTextButton); targetElement.insertBefore(subsidyText, targetElement.firstChild); subsidyTextButton.addEventListener('click', () => generateSubsidyText()); subsidyText_observer.disconnect(); break; } } } }); subsidyText_observer.observe(document.body, { childList: true, subtree: true }); } // 在页面上创建按钮 createSubsidyTextButton(); async function generateSubsidyText() { if (document.querySelector('.dropdown-modal')) { return; // 如果弹窗已存在,则不执行任何操作 } // 创建开关容器元素 const switchesContainer = document.createElement('div'); switchesContainer.classList.add('flex', 'items-center', 'justify-between', 'pb-12'); switchesContainer.style.cssText = 'position: fixed; top: 60px; right: 300px; transform: translateX(-50%); z-index: 9999; width: 200px;'; if (isTableCardURL()) { document.body.appendChild(switchesContainer); } const dropdownContainer = document.createElement('div'); dropdownContainer.style.cssText = 'position: relative; display: inline-block;'; switchesContainer.appendChild(dropdownContainer); // 调起浏览器原生的输入框,要求用户输入1~999之间的数字 let red_value = prompt("请输入1到999之间的数字:"); // let red_value = await createDropdownModal(dropdownContainer, '补贴生成'); // 检查输入是否为有效的数字,并且在1到999之间 if (red_value !== null) { // 用户没有点击取消 red_value = parseInt(red_value, 10); // 将输入转换为整数 if (isNaN(red_value) || red_value < 1 || red_value > 999) { alert("请输入1到999之间的有效数字!"); // 递归调用函数,让用户重新输入 generateSubsidyText(); } else { console.log("输入的有效数字是:", red_value); // 继续处理 testRedPacket = {}; // 初始化红包袋子 const new_docText = new DocText(); const itemId = new_docText.getCurrentProductId(); // 获取当前商品 ID testRedPacket[itemId] = red_value; // 记录红包袋子 new_docText.addDocText(); } } else { console.log("用户取消了输入"); } } // 测试使用的红包袋子 let testRedPacket = { }; // 红包补贴生成器 // debugger; // DocText 类 class DocText { constructor(idName = 'livePrice', redPacket = testRedPacket) { this.idName = idName; this.redPacket = redPacket; } // 获取 livePrice 栏目的文本内容 getDocText() { const livePriceDoc = document.getElementById(this.idName); // 获取 livePrice 栏目 if (livePriceDoc) { const liveIframe = livePriceDoc.querySelector('iframe') if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; if (body && !isNotIframeChineseName(body.innerText)) { return body.innerText; } } const currentHTML = livePriceDoc.innerText; // console.log(currentHTML); // debug return currentHTML; // 返回 livePrice 栏目的文本内容 } } // 更新 livePrice 栏目的文本内容 addDocText(text = this.preduceSubsidy()) { console.log('addDocText:', text); // debug const liveIframe = qyeryIframeForId(this.idName); if (liveIframe) { const liveIframeDocument = liveIframe.contentDocument || liveIframe.contentWindow.document; const body = liveIframeDocument.body; // 获取当前的 body 内的 HTML 内容 // const currentHTML = body.innerHTML; // 在当前 HTML 内容后面添加换行符和 "预售" 二字 const updatedHTML = text; // console.log('updatedHTML:', updatedHTML); // debug // 将更新后的 HTML 设置回 body 内 body.innerHTML += updatedHTML; } } // 按换行符分割字符串 splitStringByNewline(text = this.getDocText()) { return text.split('\n'); } // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 filterContent(arr = this.splitStringByNewline()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素 return arr.filter(item => { return priceGameplayRegex.test(item) || item.includes('到手') || item.includes('平均'); }); } // 整理数组,将价格、个数、均价分组 organizeContent(arr = this.filterContent()) { const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; let result = []; let currentGroup = []; arr.forEach(item => { if (priceGameplayRegex.test(item)) { if (currentGroup.length > 0) { // 如果当前组已经有数据,则先将其加入结果数组 result.push(currentGroup); currentGroup = []; // 清空当前组 } currentGroup.push(item); // 添加当前满足条件的元素 } else if (item.trim() !== '') { // 只添加非空行 currentGroup.push(item); } }); // 添加最后一个组,如果有的话 if (currentGroup.length > 0) { result.push(currentGroup); } console.log('organizeContent:', result); // debug return result; } // 提取关键内容 extractPriceAndQuantity(input) { // 用于匹配整个字符串的正则表达式 const priceGameplayRegex = /^(.*:)?([\d\.]+[wW+]?)元?$/; // 用于匹配“:”之前部分的正则表达式 const quantityRegex = /拍(一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|[1-9]?[0-9])件?/; // 首先匹配整个字符串 const match = input.match(priceGameplayRegex); // console.log('match:', match); // debug if (match && match[0]) { // 提取“:”之前的部分 const beforeColon = match[0].split(':')[0]; // 检查“:”之前的部分是否满足quantityRegex const quantityMatch = beforeColon.match(quantityRegex); if (quantityMatch && quantityMatch[1]) { // 获取数量部分 let quantity = quantityMatch[1]; // 将中文数字转换为阿拉伯数字 const chineseToArabic = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12, '十三': 13, '十四': 14, '十五': 15 }; if (chineseToArabic[quantity]) { quantity = chineseToArabic[quantity]; } else { quantity = parseInt(quantity, 10); // 如果已经是阿拉伯数字,直接转换 } // 返回数量和价格内容 return { price: match[2].trim(), quantity: quantity, prefix: match[1], }; } else { return { price: match[2].trim(), quantity: 1, prefix: match[1], }; } } // 如果没有匹配到或捕获组为空,则返回空对象或其他默认值 return { price: input, quantity: 1, }; } calculateMiniUint_avgPrice(input, subsidyDiscount) { // 定义正则表达式来匹配数值 const regex = /([\d\.]+)/; // 使用正则表达式进行匹配 const match = input.match(regex); if (match) { // 提取捕获组中的数值 const numberPart = match[1]; // 将数值转换为浮点数并乘以2 const doubledValue = parseFloat(numberPart) * subsidyDiscount; // 格式化新的数值为两位小数 const formattedValue = doubledValue.toFixed(2).replace(/\.?0+$/, ""); // 替换原字符串中的数值部分 const output = input.replace(numberPart, formattedValue); return output; } else { // 如果没有找到匹配项,返回错误信息或合适的默认值 return input + '*' + subsidyDiscount + '=='; } } // 获取当前商品id getCurrentProductId() { return document.getElementById('cureentItem_upLiveId').innerText; } // 获取当前商品红包 getCurrentProductRedPacket() { const productId = this.getCurrentProductId(); return this.redPacket[productId] || 0; } // 生成补贴文本 generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '') { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); // 生成补贴文本 var subsidyText = ` ${prefix} 补贴${redPacketValue * quantity} 相当于到手${subsidyedPrice} `; if (miniUint && miniUint_avgPrice) { // 更新 miniUint_avgPrice var new_miniUint_avgPrice = this.calculateMiniUint_avgPrice(miniUint_avgPrice, subsidyDiscount); var avgPriceText = ` ${miniUint} 补贴后${new_miniUint_avgPrice} `; subsidyText += avgPriceText; } return subsidyText; } isOne_generateSubsidyText(redPacketValue, originalPrice, quantity = 1, prefix = '', miniUint = '', miniUint_avgPrice = '', isOne_quantity_flag = false) { // 补贴后的价格 var subsidyedPrice = originalPrice - (redPacketValue * quantity); subsidyedPrice = subsidyedPrice.toFixed(2).replace(/\.?0+$/, ""); // 保留两位小数,去除末尾的0 // 补贴折扣力度 var subsidyDiscount = (subsidyedPrice / originalPrice); if (isOne_quantity_flag === true) { // 生成补贴文本 var subsidyText = ` 补贴${redPacketValue * quantity} 相当于到手 ${prefix} ${subsidyedPrice} `; } else { var subsidyText = ` ${prefix} ${subsidyedPrice} `; } return subsidyText; } preduceSubsidy(arr = this.organizeContent(), redPacket = this.redPacket) { console.log("arr:", arr); // debug var new_arr = []; // 存储提取数量信息的数组,包含:价格、数量、前缀文本 var indexArr = getIndexArr_togetherAndAvgPrice(arr); // “到手共”与“平均”文本的索引数组 var productId = this.getCurrentProductId(); // 获取当前商品id var redPacketValue = redPacket[productId] || 0; // 获取当前商品红包值 console.log("indexArr:", indexArr); // debug // 预处理数组内容 for (var i = 0; i < arr.length; i++) { // 处理每一组内容 if (arr[i] && Array.isArray(arr[i]) && arr[i].length > 0) { new_arr.push(this.extractPriceAndQuantity(arr[i][0])); } } // 文本生成器(传入:数组、提取数量信息的数组、“到手共”与“平均”文本的索引数组、当前商品红包值) const len = arr.length; // 数组长度 var text = ` ---------------- `; // 存储生成的文本 var isOne_quantity_flag = isOne_quantityAll(new_arr) && len > 1; // 判断是否全部数量为1 if (isOne_quantity_flag) { for (var i = 0; i < len; i++) { var isOne = i === 0; if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]], isOne); } else { text += this.isOne_generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, '', '', isOne); } } } else { for (var i = 0; i < len; i++) { if (indexArr && Array.isArray(indexArr[i]) && indexArr[i].length === 2) { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix, arr[i][indexArr[i][0]], arr[i][indexArr[i][1]]); } else { text += this.generateSubsidyText(redPacketValue, new_arr[i].price, new_arr[i].quantity, new_arr[i].prefix); } if (i < len - 1) { text += ''; } } } // // debug 输出 // var testText = ''; // testText = isOne_quantityAll(new_arr); // console.log("debug-testText:", testText); // 判断是否都是独立的sku function isOne_quantityAll(new_arr) { for (var i = 0; i < new_arr.length; i++) { if (new_arr[i].quantity !== 1) { return false; } } return true; } // 获取最后一个平均到手价在 arr 中的 index function getLastAvgPrice(son_arr) { if (!Array.isArray(son_arr)) return null; var index = -1; for (var i = 0; i < son_arr.length; i++) { if (son_arr[i].includes('平均')) { index = i; } } return index === -1 ? null : index; } // 获取最后一个到手共在 arr 中的 index function getLastTogether(son_arr) { if (!Array.isArray(son_arr)) return null; var endIndex = getLastAvgPrice(son_arr); if (endIndex === null || endIndex < 2) return null; var index = -1; for (var i = 0; i < endIndex; i++) { if (son_arr[i].includes('到手')) { index = i; } } return index === -1 ? null : index; } // 获取到手共和平均到手价在 arr 中的 index,返回二维数组 function getIndexArr_togetherAndAvgPrice(arr) { var indexArr = []; for (var i = 0; i < arr.length; i++) { var index_together = getLastTogether(arr[i]); var index_avgPrice = getLastAvgPrice(arr[i]); if (index_together !== null && index_avgPrice !== null) { indexArr[i] = [index_together, index_avgPrice]; } } return indexArr; } return text; // 函数最终返回 } } })();