标签内的内容进行着色处理
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) || plainTextSuffix.includes('补贴')) {
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) || plainTextSuffix.includes('补贴')) {
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') {
removeOldDepositTime(iframeDocument);
// 检查是否含有预售字样,检查是否是预售品
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元素。');
}
};
function removeOldDepositTime(iframeDocument) {
// 获取所有的
标签
const paragraphs = iframeDocument.getElementsByTagName('p');
// 遍历每一个
标签
for (let i = 0; i < paragraphs.length; i++) {
const p = paragraphs[i];
// 检查当前
标签是否同时包含“定金时间”和“尾款时间”
if (p.textContent.includes('定金时间') && p.textContent.includes('尾款时间')) {
// 检查是否包含prepayTime内的所有中文文本内容
let allTimesPresent = true;
for (const time in prepayTime) {
if (!p.textContent.includes(prepayTime[time])) {
allTimesPresent = false;
break;
}
}
// 如果不包含所有的时间信息,则移除这个
标签
if (!allTimesPresent) {
p.parentNode.removeChild(p);
}
}
}
}
function 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 p = doc.querySelector('#tinymce p');
if (!p) return;
const innerHTML = p.innerHTML;
let newInnerHTML = '';
let firstMatched = false; // 标志是否已经处理了第一个匹配
// 先按照标签进行分割
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;
if (!firstMatched && numericRegex.test(part)) {
// 只对第一个匹配的部分进行处理
const match = part.match(numericRegex);
if (match) {
const span = document.createElement('span');
span.innerHTML = match[0];
applyColor(span, colorLibrary.red);
// 替换第一个匹配的部分
styledPart = part.replace(numericRegex, span.outerHTML);
firstMatched = true; // 设置标志
}
}
newInnerHTML += styledPart;
}
});
}
});
p.innerHTML = newInnerHTML;
// 旧方案
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) removeOldDepositTime(iframeDocument_liveGameplay);
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("商品名-着色成功", 3000);
break;
case 'svg-icon-skuDesc':
activateIframeAndModifyStyles('skuDesc');
showNotification("商品规格-着色成功", 3000);
break;
case 'svg-icon-livePrice':
activateIframeAndModifyStyles('livePrice');
// 判断“预售信息”,辅助自动输入定金尾款时间
if (check_isHavePreSaleContent()) {
buttonClickForAddPreSaleTime();
}
showNotification("直播价-着色成功", 3000);
break;
case 'svg-icon-liveGameplay':
activateIframeAndModifyStyles('liveGameplay');
showNotification("直播玩法-着色成功", 3000);
break;
case 'svg-icon-preSetInventory':
activateIframeAndModifyStyles('preSetInventory');
showNotification("预设库存-着色成功", 3000);
break;
case 'svg-icon-inventory':
activateIframeAndModifyStyles('inventory');
showNotification("实际库存-着色成功", 3000);
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]); // 找到图片元素
const button = wrapper.querySelector('#button_getTmallPngButton'); // 找到按钮元素
if (img && !button) {
return i; // 返回匹配的编号
} else {
return -1; // 如果按钮已存在,则返回 -1
}
}
}
return -1; // 如果没有找到匹配的元素,则返回 -1
}
const wrapperClass = ['.itemImgWrap--bUt5RRLT', '.PicGallery--mainPicWrap--juPDFPo', '.mainPicWrap--Ns5WQiHr', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2'];
const imgClass = ['.itemImg--O9S7hs0i', '.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img'];
const matchingIndex = findMatchingIndex(wrapperClass, imgClass);
if (matchingIndex !== -1) {
addButton(wrapperClass, imgClass, matchingIndex);
addButton(wrapperClass, imgClass, 0);
} else {
// console.error('Wrapper element not found.');
}
function addButton(wrapperClass, imgClass, matchingIndex) {
const wrapper = document.querySelector(wrapperClass[matchingIndex]);
console.log("wrapper:", wrapper);
const old_button = wrapper.querySelector('#button_getTmallPngButton'); // 找到按钮元素
if (old_button) {
return; // 如果按钮已存在,则直接返回
}
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(22, 22, 23, .7);
color: #fff;
border: none;
border-radius: 999px;
font-family: AlibabaPuHuiTi_2_55_Regular;
backdrop-filter: saturate(180%) blur(20px); /* 添加模糊效果 */
-webkit-backdrop-filter: blur(20px); /* 兼容Safari浏览器 */
text-align: center; /* 文本居中显示 */
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease, color 0.15s ease, background-color 0.25s 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);
button.textContent = '复制成功';
button.style.backgroundColor = 'rgba(233, 233, 233, .7)'; // 按钮颜色改变
button.style.color = '#000'; // 按钮文字颜色改变
setTimeout(() => {
button.textContent = '复制图片';
button.style.backgroundColor = 'rgba(22, 22, 23, .7)'; // 按钮颜色恢复
button.style.color = '#fff'; // 按钮文字颜色恢复
}, 1500); // 1.5秒后恢复按钮文字
// 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);
}
}
}
/*
用于主播排序的辅助函数
*/
// 封装处理和生成多维数组数据的函数
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();
});
}
let itemBlockHeight = 100; // item 块高度,可根据实际情况调整
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; // 轮次计数器
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 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)) {
// console.log("getDocText:", body.innerText); // debug
return body.innerText;
}
}
const currentHTML = livePriceDoc.innerText;
// console.log("getDocText:", 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()) {
// console.log('splitStringByNewline:', text); // debug
return text.split('\n');
}
// 过滤数组,保留匹配正则表达式或包含“到手”或“平均”的元素
filterContent(arr = this.splitStringByNewline()) {
// console.log('filterContent:', arr); // debug
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 = '') {
// console.log("generateSubsidyText:", redPacketValue, originalPrice, quantity, prefix, miniUint_avgPrice); // debug
// 补贴后的价格
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; // 函数最终返回
}
}
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);
} else {
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;
}
/*
自动排序功能块
*/
let autoSortNum = ''; // 自动排序值
let AutoSortState = false; // 自动排序状态
class AutoSortItem {
sortTable = []; // 使用数组来存储排序信息
sortedIndex = []; // 使用数组存储已经排序的索引
constructor(text) {
this.text = text;
this.arr = this.changeOrderText(text);
this.lastMiniIndex = 0; // 记录上一次的最小连续数字索引
}
// 将输入的文本转换为数组
changeOrderText(text = this.text) {
// 输入样例:3,4,2,1,5;
// 预处理-清除空格、转换中文逗号
text = text.replace(/\s/g, '').replace(/,/g, ',');
// 预处理-分割字符串
const arr = text.split(',');
console.log('changeOrderText:', arr); // debug
return arr;
}
// 返回数组的最小连续数字-从 1 开始
findCurrentSmallestNumber(arr) {
if (arr.length === 0) {
return 0;
}
// 将字符串数组转换为数字数组
const numArr = arr.map(Number);
// 将数组转换为Set,以便快速查找
const set = new Set(numArr);
let currentSmallest = 1;
// 查找当前最小的连续数
while (set.has(currentSmallest)) {
currentSmallest++;
}
return currentSmallest - 1;
}
// 初始化
initSortItem() {
this.sortTable = [];
this.sortedIndex = [];
this.lastMiniIndex = 0;
autoSortNum = '';
}
// 返回与id对应的权重序号
async getItemTable(goodsDict = goodsUrlCheckArray) {
showNotification('正在更新当前信息...', 5000);
let className = '.ant-btn.css-9fw9up.ant-btn-primary';
clickButton(true, 100, undefined, className);
await new Promise(resolve => setTimeout(resolve, 4000)); // 根据实际情况调整等待时间
let sortTable = [];// 存储排序信息
// 延迟4秒后,预处理数据
try {
showNotification('即将开始自动排序...', 0);
const ids = Object.keys(goodsDict); // 获取 goodsDict 的键
for (let i = 0; i < ids.length; i++) {
const upLiveId = ids[i].toString();
let weight = this.arr[i]; // 预设权重
if (weight === undefined) {
weight = 999;
}
sortTable.push([upLiveId, weight]); // 使用 push 方法添加键值对
}
} catch (error) {
showNotification('处理 “商品信息” 时发生错误', 0);
console.error("处理 goodsDict 时发生错误:", error);
}
console.log('getItemTable:', sortTable); // debug
return sortTable;
}
async startSort() {
this.initSortItem(); // 初始化排序信息
AutoSortState = true; // 开启自动排序状态
this.sortTable = await this.getItemTable(); // 更新商品信息
for (let i = 0; i < this.sortTable.length; i++) {
showNotification(`正在处理第 ${i + 1}/${this.sortTable.length} 个商品...`, 0);
// 计算需要滚动的商品块个数
let scrollNum = this.findCurrentSmallestNumber(this.sortedIndex) - this.lastMiniIndex;
console.log('scrollNum:', scrollNum); // debug
// itemPageScroll(itemBlockHeight * scrollNum); // 向下滚动
this.lastMiniIndex = this.findCurrentSmallestNumber(this.sortedIndex); // 更新 lastMiniIndex
console.log('lastMiniIndex:', this.lastMiniIndex); // debug
// 获取将要执行的索引的商品块
const index = findItemIdForArr(this.sortTable[i][0]); // 找到索引对应的商品块
console.log('index:', index); // debug
const weight = this.sortTable[i][1]; // 预设权重
console.log('weight:', weight); // debug
const preWeight = weight * 10;// 输入权重 (默认值 * 10)
console.log('preWeight:', preWeight); // debug
autoSortNum = preWeight.toString(); // 自动排序值
click_autoInputForIndex(index); // 输入索引
console.log('click_autoInputForIndex:', index); // debug
this.sortedIndex.push(weight); // 记录已经排序的索引
console.log('sortedIndex:', this.sortedIndex); // debug
if (i + 1 >= this.sortTable.length) {
break;
}
await new Promise(resolve => setTimeout(resolve, 3000)); // 等待 3 秒
console.log('等待 3 秒'); // debug
await waitForPageToLoad(); // 等待页面加载完成
console.log('等待页面加载完成'); // debug
await new Promise(resolve => setTimeout(resolve, 1000)); // 继续等待 1 秒
console.log('继续等待 1 秒'); // debug
}
console.log('排序完成!');
showNotification('排序完成!');
this.initSortItem(); // 重置排序信息
AutoSortState = false; // 关闭自动排序状态
const button = document.getElementById('itemSort_autoSort_divButton');
if (button) {
button.click(); // 点击按钮关闭自动排序
}
}
}
/*
分词器加载
*/
// 定义一个函数用于异步加载脚本并初始化分词器
function initSegmentit(url) {
// 动态加载脚本
const loadScript = (url, callback) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => callback(null); // 使用 null 表示没有错误
script.onerror = () => callback(new Error('Failed to load script: ' + url));
document.head.appendChild(script);
};
// 加载脚本并初始化分词器
loadScript(url, (err) => {
if (err) {
console.error(err.message);
return;
}
// 确保页面完全加载后再执行脚本
window.addEventListener('load', function () {
// 检查 Segmentit 对象是否已定义
if (typeof Segmentit === 'undefined') {
console.error('Segmentit is not defined. Please check if the library is loaded correctly.');
} else {
console.log('Segmentit is loaded:', segmenter); // debug
}
});
});
}
// 调用函数并传入 URL 和希望使用的全局变量名称
initSegmentit('https://cdn.jsdelivr.net/npm/segmentit@2.0.3/dist/umd/segmentit.js');
let debug_segmentit = false; // 是否开启分词器调试模式
/*
分词器加载完毕
*/
const testText_1 = '珍珠粉黑灵芝紧致白面膜组合';
const testText_2 = '1g+5g珍珠粉黑灵芝紧致白面膜组合';
function segmentText(text, returnSentence = false) {
// 实例化分词器
const segmentit = Segmentit.useDefault(new Segmentit.Segment());
// 获取分词后的对象
const result = segmentit.doSegment(text);
// 输出分词后的文本
let sentence = ''; // 存储分词后的文本
if (returnSentence) {
// 返回分词后的句子
sentence = result.map(item => item.w).join(' ');
} else {
// 返回分词后的数组
sentence = result.map(item => item.w);
}
console.log(sentence);
return sentence;
}
function calcSimilarity(title1, title2) {
// 将分词结果转换为单词数组
const wordArray1 = segmentText(title1);
const wordArray2 = segmentText(title2);
// 构建词汇表
const vocabulary = new Set([...wordArray1, ...wordArray2]);
const vocabArray = Array.from(vocabulary);
// 构建向量
function createVector(wordArray, vocabArray) {
const vector = new Array(vocabArray.length).fill(0);
for (let i = 0; i < wordArray.length; i++) {
const index = vocabArray.indexOf(wordArray[i]);
if (index !== -1) {
vector[index]++;
}
}
return vector;
}
const vector1 = createVector(wordArray1, vocabArray);
const vector2 = createVector(wordArray2, vocabArray);
// 计算余弦相似度
function cosineSimilarity(vec1, vec2) {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vec1.length; i++) {
dotProduct += vec1[i] * vec2[i];
normA += vec1[i] * vec1[i];
normB += vec2[i] * vec2[i];
}
if (normA === 0 || normB === 0) {
return 0; // 防止除以零
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
const similarity = cosineSimilarity(vector1, vector2);
console.log('calcSimilarity:', similarity); // debug
return similarity;
}
// 构建相似度矩阵
function buildSimilarityMatrix(table1, table2) {
const n = table1.length;
const m = table2.length;
const similarityMatrix = Array.from({ length: n }, () => Array(m).fill(0));
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const shopSim = calcSimilarity(table1[i]['店铺名'], table2[j]['店铺名']);
const titleSim = calcSimilarity(table1[i]['商品标题'], table2[j]['商品标题']);
const totalSim = 0.35 * shopSim + 0.65 * titleSim;
similarityMatrix[i][j] = totalSim;
}
}
return similarityMatrix;
}
// 使用有序贪心算法进行匹配
function matchItemsWithGreedy(table1, table2) {
const similarityMatrix = buildSimilarityMatrix(table1, table2);
const n = table1.length;
const m = table2.length;
const matches = [];
const usedCols = new Set();
for (let i = 0; i < n; i++) {
let maxSim = -1;
let bestCol = -1;
for (let j = 0; j < m; j++) {
if (usedCols.has(j)) continue;
const sim = similarityMatrix[i][j];
if (sim > maxSim) {
maxSim = sim;
bestCol = j;
}
}
if (bestCol !== -1) {
matches.push([i, bestCol]);
usedCols.add(bestCol);
}
}
return matches;
}
// 使用改进的匈牙利算法进行匹配
function matchItemsWithHungarian(table1, table2) {
const similarityMatrix = buildSimilarityMatrix(table1, table2);
const n = table1.length;
const m = table2.length;
const costMatrix = similarityMatrix.map(row => row.map(val => -val)); // 转换为成本矩阵
const labelX = new Array(n).fill(0);
const labelY = new Array(m).fill(0);
const match = new Array(m).fill(-1);
const slack = new Array(m).fill(Number.MAX_SAFE_INTEGER);
const visitedX = new Array(n).fill(false);
const visitedY = new Array(m).fill(false);
// 初始化标签
for (let i = 0; i < n; i++) {
labelX[i] = Math.max(...costMatrix[i]);
}
function findAugmentingPath(i) {
visitedX[i] = true;
for (let j = 0; j < m; j++) {
if (visitedY[j]) continue;
const delta = labelX[i] + labelY[j] - costMatrix[i][j];
if (delta === 0) {
visitedY[j] = true;
if (match[j] === -1 || findAugmentingPath(match[j])) {
match[j] = i;
return true;
}
} else {
slack[j] = Math.min(slack[j], delta);
}
}
return false;
}
for (let i = 0; i < n; i++) {
while (true) {
slack.fill(Number.MAX_SAFE_INTEGER);
visitedX.fill(false);
visitedY.fill(false);
if (findAugmentingPath(i)) break;
let delta = Number.MAX_SAFE_INTEGER;
for (let j = 0; j < m; j++) {
if (!visitedY[j]) delta = Math.min(delta, slack[j]);
}
for (let j = 0; j < n; j++) {
if (visitedX[j]) labelX[j] -= delta;
}
for (let j = 0; j < m; j++) {
if (visitedY[j]) labelY[j] += delta;
}
}
}
const matches = match.map((col, row) => [row, col]);
return matches;
}
// 输出结果
function printMatches(matches, table1, table2) {
for (const [i, index] of matches) {
const item1 = table1[i];
const item2 = table2[index];
const score = calcSimilarity(item1['商品标题'], item2['商品标题']);
console.log(`[${i + 1}]表1商品: ${item1['商品标题']}\n表2位置: ${index + 1} (商品: ${item2['商品标题']}) [相似度: ${score.toFixed(2)}]`);
}
}
function printIndex(matches) {
const arr = matches.map(index => index[1] + 1);
console.log("arrLen:", arr.length);
console.log("arr:", arr);
console.log("arr_no_dup:", new Set(arr).size);
const arrCheck = [...new Set(arr)];
console.log("arrCheck:", arrCheck.sort((a, b) => a - b));
}
// 测试数据
const table1 = [
{ "商品标题": "水牛纯牛奶", "店铺名": "爱泡澡的水牛旗舰店" },
{ "商品标题": "红养豆浆粉", "店铺名": "九阳豆浆旗舰店" },
{ "商品标题": "芙丝天然矿泉水", "店铺名": "华彬旗舰店" },
{ "商品标题": "正宗天马老树陈皮", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "初心肉桂大红袍组合", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "茗潮宋礼礼盒", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "铁观音 乌龙茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "2015年白毫银针", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "五彩罐岩茶组合", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "武夷山正山小种", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "茉莉花茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "碧螺春", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "鸿运金骏眉礼盒", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "针王 白茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "正坑牛肉 乌龙茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "首字号茶礼 乌龙茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "桐木关老枞 红茶礼盒", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "吮指鸡肉条", "店铺名": "牧匠食品旗舰店" },
{ "商品标题": "法丽兹黄油小花曲奇173g*2罐", "店铺名": "法丽兹食品旗舰店" },
{ "商品标题": "芡实糕", "店铺名": "杨先生旗舰店" },
{ "商品标题": "捞汁豆腐", "店铺名": "杨生记食品旗舰店" },
{ "商品标题": "茶香师禅茶系列香水", "店铺名": "茶香师旗舰店" },
{ "商品标题": "宝玑米手甲同护护手霜", "店铺名": "puljim宝玑米旗舰店" },
{ "商品标题": "原制奶酪饼", "店铺名": "潮香村食品旗舰店" },
{ "商品标题": "春雪流心鸡球", "店铺名": "春雪食品旗舰店" },
{ "商品标题": " 网红糯米笋", "店铺名": "蔚鲜来旗舰店" },
{ "商品标题": "羊肚菌", "店铺名": "瑞利来旗舰店" },
{ "商品标题": "大罐粥料", "店铺名": "瑞利来旗舰店" },
{ "商品标题": "新会柑普茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "桂花金骏眉", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "金丝鸡排", "店铺名": "正新食品旗舰店" },
{ "商品标题": "酱牛肉", "店铺名": "阿品旗舰店" },
{ "商品标题": "茶和天下", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "丹东99草莓", "店铺名": "时予心愿旗舰店" },
{ "商品标题": "海乡集即食海参", "店铺名": "海乡集旗舰店" },
{ "商品标题": "果乐果香夹心饼干", "店铺名": "嘉士利旗舰店" },
{ "商品标题": "多味香瓜子", "店铺名": "华味亨旗舰店" },
{ "商品标题": "酥小娘核桃布里奥斯面包", "店铺名": "酥小娘旗舰店" },
{ "商品标题": "芋泥乳酪派", "店铺名": "lilygarden荷家旗舰店" },
{ "商品标题": "魔芋荞麦鸡肉膳食包+魔芋韭菜鸡蛋膳食包", "店铺名": "巨诺旗舰店" },
{ "商品标题": "智利进口车厘子", "店铺名": "淘宝买菜农场直发" },
{ "商品标题": "寿南瓜子", "店铺名": "苏太太食品旗舰店" },
{ "商品标题": "石浪电陶炉+枫梭提梁壶围炉套装", "店铺名": "唐丰旗舰店" },
{ "商品标题": "玺棠一体茶盘+素影煮茶壶+圆满素语西施壶9头", "店铺名": "唐丰旗舰店" },
{ "商品标题": "纯钛茶水分离杯", "店铺名": "天猫超市" },
{ "商品标题": "老卤鸭肫", "店铺名": "旺家福旗舰店" },
{ "商品标题": "夏威夷果可可脆", "店铺名": "菓然匠子旗舰店" },
{ "商品标题": "2014荒野贡眉", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "风干牛肉干九成干", "店铺名": "蒙亮旗舰店" },
{ "商品标题": "农家乌鸡950g*3", "店铺名": "淘宝买菜农场直发" },
{ "商品标题": "潮庭优选牛肉丸牛筋丸双拼组合", "店铺名": "潮庭旗舰店" },
{ "商品标题": "早餐牛肉饼", "店铺名": "潮迹旗舰店" },
{ "商品标题": "围炉煮茶器具全套", "店铺名": "尚言坊旗舰店" },
{ "商品标题": "点心盘", "店铺名": "拓土旗舰店" },
];
const table2 = [
{ "商品标题": "苏太太百寿南瓜子", "店铺名": "苏太太食品旗舰店" },
{ "商品标题": "捞汁豆腐", "店铺名": "杨生记食品旗舰店" },
{ "商品标题": "芡实糕多口味组合", "店铺名": "杨先生旗舰店" },
{ "商品标题": "法丽兹黄油小花曲奇", "店铺名": "法丽兹食品旗舰店" },
{ "商品标题": "水牛纯牛奶", "店铺名": "爱泡澡的水牛旗舰店" },
{ "商品标题": "魔芋荞麦鸡肉膳食包+魔芋韭菜鸡蛋膳食包", "店铺名": "巨诺旗舰店" },
{ "商品标题": "九阳豆浆红养豆浆粉", "店铺名": "九阳豆浆旗舰店" },
{ "商品标题": "陈皮绿豆百合银耳粥1.308kg*1罐/陈皮红豆粥1.010kg*1罐/1.5kg/罐/干贝虾仁粥1.1kg/罐/紫薯黑米粥1.48kg/罐", "店铺名": "瑞利来旗舰店" },
{ "商品标题": "酥小娘核桃布里奥斯面包", "店铺名": "酥小娘旗舰店" },
{ "商品标题": "夏威夷果可可脆", "店铺名": "菓然匠子旗舰店" },
{ "商品标题": "果乐果香夹心饼干", "店铺名": "嘉士利旗舰店" },
{ "商品标题": "芋泥乳酪派", "店铺名": "lilygarden荷家旗舰店" },
{ "商品标题": "打手瓜子", "店铺名": "华味亨旗舰店" },
{ "商品标题": "旺家福老卤鸭肫", "店铺名": "旺家福旗舰店" },
{ "商品标题": "茶香师禅茶系列香水 和合之境 早春之茶 正如初念", "店铺名": "茶香师旗舰店" },
{ "商品标题": "宝玑米微氛手甲精华霜囤货装", "店铺名": "puljim宝玑米旗舰店" },
{ "商品标题": "【王炸】车厘子 官补115", "店铺名": "淘宝买菜农场直发" },
{ "商品标题": "丹东99草莓", "店铺名": "时予心愿旗舰店" },
{ "商品标题": "吮指鸡肉条", "店铺名": "牧匠食品旗舰店" },
{ "商品标题": "风干牛肉干九成干", "店铺名": "蒙亮旗舰店" },
{ "商品标题": "围炉煮茶", "店铺名": "唐丰旗舰店" },
{ "商品标题": "特美刻茶水分离杯 补贴50", "店铺名": "" },
{ "商品标题": "茶和天下品鉴装", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "初心组合 补贴30", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "【王炸】天马陈皮220g 补贴50", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "新会柑普茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "鸿运金骏眉礼盒 补贴50", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "桐木关老枞红茶礼盒 补贴20", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "【王炸】2015年老银针", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "正坑牛肉/正坑牛肉礼盒装 补贴100", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "2014年荒野贡眉 补贴15", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "茗潮宋礼 补贴20", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "针王 补贴50", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "茉莉花茶", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "桂花金骏眉", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "茶具套装", "店铺名": "唐丰旗舰店" },
{ "商品标题": "五彩罐岩茶组合 补贴30", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "武夷山正山小种", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "首字号茶礼512g 补贴30", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "铁观音", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "碧螺春", "店铺名": "望峰茶叶旗舰店" },
{ "商品标题": "VOSS/芙丝天然矿泉水", "店铺名": "华彬旗舰店" },
{ "商品标题": "条纹月笼壶", "店铺名": "尚言坊" },
{ "商品标题": "海乡集大连即食海参 补贴25", "店铺名": "海乡集旗舰店" },
{ "商品标题": "潮庭优选双拼牛丸组合", "店铺名": "潮庭旗舰店" },
{ "商品标题": "农家散养乌鸡", "店铺名": "淘宝买菜" },
{ "商品标题": "瑞利来羊肚菌", "店铺名": "瑞利来旗舰店" },
{ "商品标题": "流心鸡球400g", "店铺名": "春雪食品旗舰店" },
{ "商品标题": "奶酪饼", "店铺名": "潮香村食品旗舰店" },
{ "商品标题": "网红糯米笋", "店铺名": "蔚鲜来旗舰店" },
{ "商品标题": "正新金丝鸡排", "店铺名": "正新食品旗舰店" },
{ "商品标题": "阿品酱牛肉", "店铺名": "阿品旗舰店" },
];
// setTimeout(() => {
// // calcSimilarity(testText_1, testText_2);
// console.log('开始匹配商品...');
// // const matches = matchItemsWithGreedy(table1, table2);
// const matches = matchItemsWithHungarian(table1, table2);
// printMatches(matches, table1, table2);
// printIndex(matches);
// }, 10000);
})();