// ==UserScript==
// @name 美团搬菜(支持超市水果店)
// @description 从美团将菜单导入到搬菜平台
// @version v2.0.1MAX
// @author mirari、ChengPP(后续)
// @copyright 2023, mirari (https://github.com/mirari)
// @match https://cactivityapi-sc.waimai.meituan.com/h5*
// @match https://h5.waimai.meituan.com/waimai/mindex*
// @run-at document-idle
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @connect raw.githubusercontent.com
// @connect mv.nianxiang.net.cn
// @connect localhost
// @connect *
// @icon https://himg.bdimg.com/sys/portrait/item/pp.1.61637635.q_9U7gFy_biR3yojcvZygw.jpg?tt=1732025929684
// @namespace https://greasyfork.org/users/1436563
// @downloadURL https://update.greasyfork.icu/scripts/527328/%E7%BE%8E%E5%9B%A2%E6%90%AC%E8%8F%9C%EF%BC%88%E6%94%AF%E6%8C%81%E8%B6%85%E5%B8%82%E6%B0%B4%E6%9E%9C%E5%BA%97%EF%BC%89.user.js
// @updateURL https://update.greasyfork.icu/scripts/527328/%E7%BE%8E%E5%9B%A2%E6%90%AC%E8%8F%9C%EF%BC%88%E6%94%AF%E6%8C%81%E8%B6%85%E5%B8%82%E6%B0%B4%E6%9E%9C%E5%BA%97%EF%BC%89.meta.js
// ==/UserScript==
(function () {
'use strict';
let food;
let autoGetting = false;
let isImporting = false;
const createButtons = () => {
const mainBtn = document.createElement('button');
mainBtn.innerHTML = '';
mainBtn.style = `
position: fixed;
top: 2vw;
right: 2vw;
z-index: 999999;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
transition: transform 0.3s ease-in-out, color 0.3s ease-in-out;
`;
const parseBtn = document.createElement('button');
parseBtn.innerHTML = '解析新结构分类';
parseBtn.style = `
position: fixed;
top: -999vw;
right: -300vw;
transition: right 0.3s ease-in-out, transform 0.3s ease-in-out, background-color 0.3s ease-in-out, color 0.3s ease-in-out;
z-index: 999999;
background: #ffbd27;
border-radius: 1.6vw;
border: none;
padding: 1.5vw;
color: white;
font-weight: bold;
`;
const btnImport = document.createElement('button');
btnImport.innerHTML = '导入店铺菜单';
btnImport.style = `
position: fixed;
top: 7vw;
right: -300vw;
transition: right 0.3s ease-in-out, transform 0.3s ease-in-out, background-color 0.3s ease-in-out, color 0.3s ease-in-out;
z-index: 999999;
background: #ffbd27;
border-radius: 1.6vw;
border: none;
padding: 1.5vw;
color: white;
font-weight: bold;
`;
const btnShowRawData = document.createElement('button');
btnShowRawData.innerHTML = '获取原始数据';
btnShowRawData.style = `
position: fixed;
top: 12vw;
right: -300vw;
transition: right 0.3s ease-in-out, transform 0.3s ease-in-out, background-color 0.3s ease-in-out, color 0.3s ease-in-out;
z-index: 999999;
background: #ffbd27;
border-radius: 1.6vw;
border: none;
padding: 1.5vw;
color: white;
font-weight: bold;
`;
const btnShowCategories = document.createElement('button');
btnShowCategories.innerHTML = '获取商品状态';
btnShowCategories.style = `
position: fixed;
top: 17vw;
right: -300vw;
transition: right 0.3s ease-in-out, transform 0.3s ease-in-out, background-color 0.3s ease-in-out, color 0.3s ease-in-out;
z-index: 999999;
background: #ffbd27;
border-radius: 1.6vw;
border: none;
padding: 1.5vw;
color: white;
font-weight: bold;
`;
return { mainBtn, parseBtn, btnImport, btnShowRawData, btnShowCategories };
};
const { mainBtn, parseBtn, btnImport, btnShowRawData, btnShowCategories } = createButtons();
let isExpanded = false;
mainBtn.onclick = () => {
if (isExpanded) {
parseBtn.style.right = '-300vw';
btnImport.style.right = '-300vw';
btnShowRawData.style.right = '-300vw';
btnShowCategories.style.right = '-300vw';
} else {
parseBtn.style.right = '2vw';
btnImport.style.right = '2vw';
btnShowRawData.style.right = '2vw';
btnShowCategories.style.right = '2vw';
}
isExpanded = !isExpanded;
mainBtn.style.transform = isExpanded ? 'scale(1.2)' : 'scale(1)';
};
parseBtn.onclick = async () => {
if (window.location.href.startsWith('https://cactivityapi-sc.waimai.meituan.com/h5/sub-trade/restaurant/restaurant?')) {
parseBtn.style.backgroundColor = '#ff8000';
parseBtn.style.color = 'white';
parseBtn.innerHTML = '解析中...';
await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟解析过程
alert('解析新结构分类成功...');
location.reload();
} else {
alert('当前页面不需要解析新结构分类。');
}
};
btnImport.onclick = () => {
if (food) {
const tagCount = food.data.food_spu_tags.length;
const spuCount = food.data.food_spu_tags.reduce((sum, tag) => sum + tag.spus.length, 0);
console.log(food);
console.log('tagCount', tagCount);
console.log('spuCount', spuCount);
const incompleteTags = food.data.food_spu_tags.filter(tag => !tag.spus.length && tag.attempted !== true);
const noProductTags = food.data.food_spu_tags.filter(tag => !tag.spus.length && tag.attempted === true);
const incompleteTagCount = incompleteTags.length;
const noProductTagCount = noProductTags.length;
let tip = '';
if (incompleteTagCount > 0 || noProductTagCount > 0) {
if (window.location.href.includes('https://h5.waimai.meituan.com/waimai/mindex/menu?')) {
tip = `🌟Tip1:当前为分页菜单,请点击黄色的标签分类获取商品。\n🌟Tip2:当店铺商品数较多时候,请浏览完整商品页面,避免获取缺失!!!`;
} else if (window.location.href.includes('https://cactivityapi-sc.waimai.meituan.com/h5') ) {
tip = `🌟Tip1:商品获取情况,可用查看已获取分类按钮查看详情...\n🌟Tip2:新结构店铺商品数较多,请浏览完整商品页面,避免获取缺失!!!`;
}
}
let confirmMessage = ``;
if (tip) {
confirmMessage += `${tip}\n\n`;
}
confirmMessage += `获取到分类${tagCount}个,商品共计${spuCount}个(存在重复计入)。\n`;
if (incompleteTagCount > 0) {
confirmMessage += `注意:当前还有${incompleteTagCount}个分类未获取完整信息。\n`;
}
if (noProductTagCount > 0) {
confirmMessage += `注意:当前有${noProductTagCount}个分类无商品。\n`;
}
confirmMessage += `是否导入到搬店平台?`;
if (confirm(confirmMessage)) {
btnImport.style.transform = 'scale(1.2)';
btnImport.style.backgroundColor = '#ff8000';
btnImport.style.color = 'white';
btnImport.innerHTML = '正在导入中...';
importData();
}
} else {
alert('未能监听到菜单数据。');
}
};
btnShowRawData.onclick = () => {
if (food) {
const allFoodData = JSON.stringify(food, null, 2);
const blob = new Blob([allFoodData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
} else {
alert('未能监听到菜单数据。');
}
};
btnShowCategories.onclick = () => {
if (food) {
const completeTags = food.data.food_spu_tags.filter(tag => tag.spus.length);
const completeTagCount = completeTags.length;
const totalSpuCount = completeTags.reduce((sum, tag) => sum + tag.spus.length, 0);
const incompleteTags = food.data.food_spu_tags.filter(tag => !tag.spus.length && tag.attempted !== true);
const incompleteTagCount = incompleteTags.length;
const incompleteTagNames = incompleteTags.map(tag => tag.name).join(', ');
const noProductTags = food.data.food_spu_tags.filter(tag => !tag.spus.length && tag.attempted === true);
const noProductTagCount = noProductTags.length;
const noProductTagNames = noProductTags.map(tag => tag.name).join(', ');
const totalTagCount = food.data.food_spu_tags.length;
const container = document.createElement('div');
container.style = `
max-height: 80vh;
overflow-y: auto;
padding: 10px;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000000;
position: fixed;
top: 15vw;
left: 50%;
transform: translateX(-50%);
width: 80vw;
max-width: 600px;
border-radius: 1.6vw;
`;
const statsContainer = document.createElement('div');
statsContainer.style = `
margin-bottom: 10px;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px;
`;
const statsText = `
总分类数: ${totalTagCount}
已获取分类数: ${completeTagCount}
未获取分类数: ${incompleteTagCount}
无商品分类数: ${noProductTagCount}
已获取商品数: ${totalSpuCount}
`; statsContainer.innerHTML = statsText; const filterContainer = document.createElement('div'); filterContainer.style = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; `; const filterOptions = ['全部', '已获取', '未获取', '无商品']; const filterSelect = document.createElement('select'); filterSelect.style = ` padding: 5px; border: 1px solid #ccc; border-radius: 5px; `; filterOptions.forEach(option => { const opt = document.createElement('option'); opt.value = option; opt.text = option; filterSelect.appendChild(opt); }); filterSelect.onchange = () => { updateTable(filterSelect.value); }; filterContainer.appendChild(filterSelect); const closeButton = document.createElement('button'); closeButton.innerHTML = '关闭'; closeButton.style = ` padding: 5px 10px; background-color: #ffbd27; color: white; border: none; border-radius: 5px; cursor: pointer; `; closeButton.onclick = () => { document.body.removeChild(container); }; filterContainer.appendChild(closeButton); const table = document.createElement('table'); table.style = 'width: 100%; border-collapse: collapse;'; const thead = document.createElement('thead'); thead.innerHTML = `