Greasy Fork

Greasy Fork is available in English.

饿了么搬菜

抓取ele.me页面上的分类及商品信息并统计

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           饿了么搬菜
// @description     抓取ele.me页面上的分类及商品信息并统计
// @version         v1.0.1
// @author          ChengPP
// @match           https://h5.ele.me/*
// @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
// @require         https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.5/xlsx.full.min.js
// @namespace http://greasyfork.icu/users/1436563
// ==/UserScript==

(function() {

  'use strict';

  let categories = {};
  const specificationsStorage = {};  // 使用一个对象来代替localStorage存储规格信息
  let nextSpuId = 1;  // 用于生成唯一的SPU ID
  let food = null;  // 存储最终的数据结构
  let shopInfo = {};  // 存储门店信息
  let poiIdStr = 'ZhengJinCheng';  // 默认值

  const createMainButton = () => {
    const mainBtn = document.createElement('button');
    mainBtn.innerHTML = '<img src="https://himg.bdimg.com/sys/portrait/item/pp.1.61637635.q_9U7gFy_biR3yojcvZygw.jpg?tt=1732025929684" alt="☰" style="width: 15px; height: 15px;">';
    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;
    `;
    return mainBtn;
  };

  const createImportButton = () => {
    const btn = document.createElement('button');
    btn.innerHTML = '导入到搬点平台';
    btn.style = `
      position: fixed;
      top: 8vw;
      right: -300vw;
      width: 150px;
      height: 40px;
      z-index: 999999;
      background: #4CAF50;
      border: none;
      border-radius: 5px;
      color: white;
      font-weight: bold;
      cursor: pointer;
      transition: opacity 0.3s ease-in-out;
      opacity: 0;
    `;
    return btn;
  };

  const createParseButton = () => {
    const btn = document.createElement('button');
    btn.innerHTML = '获取商品状态';
    btn.style = `
      position: fixed;
      top: 14vw;
      right: -300vw;
      width: 150px;
      height: 40px;
      z-index: 999999;
      background: #ffbd27;
      border: none;
      border-radius: 5px;
      color: white;
      font-weight: bold;
      cursor: pointer;
      transition: opacity 0.3s ease-in-out;
      opacity: 0;
    `;
    return btn;
  };

  const createDataConversionButton = () => {
    const btn = document.createElement('button');
    btn.innerHTML = '查看数据';
    btn.style = `
      position: fixed;
      top: 20vw;
      right: -300vw;
      width: 150px;
      height: 40px;
      z-index: 999999;
      background: #9E9E9E;
      border: none;
      border-radius: 5px;
      color: white;
      font-weight: bold;
      cursor: pointer;
      transition: opacity 0.3s ease-in-out;
      opacity: 0;
    `;
    return btn;
  };

  const mainBtn = createMainButton();
  const btnImport = createImportButton();
  const parseBtn = createParseButton();
  const btnDataConversion = createDataConversionButton();

  document.body.appendChild(mainBtn);
  document.body.appendChild(btnImport);
  document.body.appendChild(parseBtn);
  document.body.appendChild(btnDataConversion);

  let isExpanded = false;
  mainBtn.onclick = () => {
    if (isExpanded) {
      btnImport.style.right = '-300vw';
      parseBtn.style.right = '-300vw';
      btnDataConversion.style.right = '-300vw';
    } else {
      btnImport.style.right = '2vw';
      parseBtn.style.right = '2vw';
      btnDataConversion.style.right = '2vw';
      btnImport.style.opacity = '1';
      parseBtn.style.opacity = '1';
      btnDataConversion.style.opacity = '1';
    }
    isExpanded = !isExpanded;
    mainBtn.style.transform = isExpanded ? 'scale(1.2)' : 'scale(1)';
    mainBtn.style.width = isExpanded ? '30px' : '15px';
    mainBtn.style.height = isExpanded ? '30px' : '15px';
  };

  btnImport.onclick = () => {
    if (Object.keys(categories).length > 0) {
      const totalItems = Object.values(categories).reduce((sum, items) => sum + items.length, 0);
      const emptyCategories = Object.entries(categories)
        .filter(([id, items]) => items.length === 0)
        .map(([id]) => categories[id][0]?.name || id)
        .join(', ');
      const message = emptyCategories ? `还有如下分类未获取:${emptyCategories}(共${emptyCategories.split(', ').length}个),${shopInfo.name}目前获取到商品${totalItems}个,是否继续导入?` : `${shopInfo.name}目前获取到商品${totalItems}个,是否确认导入到搬点平台?`;
      if (confirm(message)) {
        btnImport.innerHTML = '正在导入商品中...';
        btnImport.style.backgroundColor = '#FF5722';
        btnImport.style.transform = 'scale(1.2)';

        convertData();  // 调用数据转换函数

        let xhr = new XMLHttpRequest();
        try {
          xhr.open("POST", 'https://mv.nianxiang.net.cn/api/admin/move/task/open/import', true);
          xhr.setRequestHeader("Content-Type", "application/json");
          xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
              const res = JSON.parse(xhr.responseText);
              console.log(res);
              alert(res.data);
              btnImport.innerHTML = '导入到搬点平台';
              btnImport.style.backgroundColor = '#4CAF50';
              btnImport.style.transform = 'scale(1)';
            }
          };
          const data = JSON.stringify({
            raw: JSON.stringify(food),
          });
          xhr.send(data);
        } catch (err) {
          console.log(err);
          alert('请求出错:' + err.message);
          btnImport.innerHTML = '导入到搬点平台';
          btnImport.style.backgroundColor = '#4CAF50';
          btnImport.style.transform = 'scale(1)';
        }
      }
    } else {
      alert('尚未获取到分类及商品信息,请先浏览页面加载相关信息。');
    }
  };

  parseBtn.onclick = () => {
    if (Object.keys(categories).length > 0) {
      importSpecifications();
      showStatisticsTable();
    } else {
      getCategoryData();
      getMenuItemData();
      getShopInfo();
      getPoiIdStr();
      parseBtn.innerHTML = '获取商品状态';
      parseBtn.style.backgroundColor = '#ffbd27';
      parseBtn.style.transform = 'scale(1)';
    }
  };

  btnDataConversion.onclick = () => {
    if (Object.keys(categories).length > 0) {
      convertDataAndShow();
    } else {
      alert('尚未获取到分类及商品信息,请先浏览页面加载相关信息。');
    }
  };

  const getCategoryData = () => {
    const categoryElements = document.querySelectorAll('.sideList--item .sideList--item-text');
    categories = {};
    categoryElements.forEach(element => {
      const categoryName = element.textContent.trim();
      categories[categoryName] = [];
    });
    console.log('分类数据获取完成:', categories);
  };

  const getMenuItemData = () => {
    const menuItemElements = document.querySelectorAll('[data-cate-name]');
    menuItemElements.forEach(element => {
      const categoryName = element.getAttribute('data-cate-name');
      const fractionElement = element.querySelector('.menuItem--info-price-text.fraction');
      const fraction = fractionElement ? parseFloat(fractionElement.textContent) : 0;
      const item = {
        id: nextSpuId++,
        name: element.getAttribute('data-food-detail-title'),
        min_price: parseFloat(element.querySelector('.menuItem--info-price-text')?.textContent.replace('¥', '')) + (fraction ? fraction : 0),
        picture: element.getAttribute('data-food-detail-img'),
        attrs: specificationsStorage[element.getAttribute('data-food-detail-title')] || [],
        sku_label: element.querySelector('.menuItem--info-price .menuItem--info-price-text')?.textContent || '规格',
        skus: []
      };

      if (categories[categoryName]) {
        categories[categoryName].push(item);
      }
    });
    console.log('商品数据获取完成:', categories);
  };

  const importSpecifications = () => {
    const popupElements = document.querySelectorAll('.sku__wrapper, .sku-wrapper');
    popupElements.forEach(popupElement => {
      const itemName = popupElement.querySelector('.sku--header-title, .sku-header .sku-header-title')?.textContent?.trim();
      const specGroups = popupElement.querySelectorAll('.sku--group, .sku-group');

      const specifications = [];
      specGroups.forEach(group => {
        const specTitle = group.querySelector('.sku--body_h2, .sku-group-title')?.textContent?.trim();
        const options = Array.from(group.querySelectorAll('.sku-option__root, .sku-group-item')).map(optionElement => {
          const nameSelector = optionElement.querySelector('.ml-ellipsis.lh-32.font-24.color-19, .sku-group-item-title, .lh-30');
          const priceSelector = optionElement.querySelector('.option-price, .sku-group-item-price');
          return {
            name: nameSelector?.textContent?.trim(),
            price: priceSelector?.textContent || '+ ¥0'
          };
        });
        specifications.push({
          name: specTitle,
          values: options.map(option => ({
            id: option.name,  // 这里假设每个选项的名字是唯一的,可以用作ID
            value: option.name
          }))
        });
      });

      specificationsStorage[itemName] = specifications;  // 将规格信息存储在内存中

      let matched = false;
      Object.values(categories).forEach(categoryItems => {
        const item = categoryItems.find(item => item.name === itemName);
        if (item && !item.specsImported) {
          item.attrs = item.attrs.concat(specifications);
          item.specsImported = true;
          matched = true;

          const specInfo = JSON.stringify(specifications, null, 2);
          console.log(`商品 "${itemName}" 的规格信息导入完成:\n${specInfo}`);
        }
      });

      if (!matched) {
        console.log(`未找到与商品 "${itemName}" 匹配的信息`);
      }
    });
  };

  const showStatisticsTable = () =>
{
    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;
    `;

    // 获取店铺名称和logo
  const shopNameElementNew = document.querySelector('.shop-header-info-card__name.mor-comp-view');
  const shopLogoElementNew = document.querySelector('.shop-header-info-card__logo.mor-comp-view');

  const shopNameElementOld = document.getElementById('shic2-shop-name');
  const shopLogoElementOld = document.querySelector('.shic2-shop-logo');

  let shopName = '';
  let shopLogoUrl = '';

  if (shopNameElementNew && shopLogoElementNew) {
    shopName = shopNameElementNew.textContent.trim().replace(/&amp;/g, '&');
    shopLogoUrl = shopLogoElementNew.style.backgroundImage.replace(/url\(|\)|"/g, '') ;
  } else if (shopNameElementOld && shopLogoElementOld) {
    shopName = shopNameElementOld.textContent.trim().replace(/&amp;/g, '&');
    shopLogoUrl = shopLogoElementOld.getAttribute('src') || 'https://himg.bdimg.com/sys/portrait/item/pp.1.61637635.q_9U7gFy_biR3yojcvZygw.jpg?tt=1732025929684';
  } else {
    shopName = '未知店铺';
    shopLogoUrl = 'https://himg.bdimg.com/sys/portrait/item/pp.1.61637635.q_9U7gFy_biR3yojcvZygw.jpg?tt=1732025929684';
  }

    const headerContainer = document.createElement('div');
    headerContainer.style = `
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
    `;

    const logoImg = document.createElement('img');
    logoImg.src = shopLogoUrl;
    logoImg.alt = '店铺logo';
    logoImg.style = `
      width: 50px;
      height: 50px;
      object-fit: cover;
      margin-right: 10px;
      border-radius: 5px;
    `;

    const shopNameSpan = document.createElement('span');
    shopNameSpan.innerText = shopName;
    shopNameSpan.style = `
      font-size: 16px;
      font-weight: bold;
    `;

    headerContainer.appendChild(logoImg);
    headerContainer.appendChild(shopNameSpan);

    const buttonContainer = document.createElement('div');
    buttonContainer.style = `
      display: flex;
      gap: 10px;
      align-items: center;
    `;

    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);
    };

    const exportExcelButton = document.createElement('button');
    exportExcelButton.innerHTML = '导出为Excel';
    exportExcelButton.style = `
      margin-right: 10px;
      padding: 5px 10px;
      background-color: #2196F3;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    `;
    exportExcelButton.onclick = () => {
    convertData();
      if (!food || !food.data) {
        alert('没有可用的商品数据!');
        return;
      }
      exportToExcel(food);
    };

    const viewOnlineButton = document.createElement('button');
    viewOnlineButton.innerHTML = '查看商品详细';
    viewOnlineButton.style = `
      padding: 5px 10px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    `;
    viewOnlineButton.onclick = () => {
    convertData();
      if (!food || !food.data) {
        alert('没有可用的商品数据!');
        return;
      }
      openViewPage(food);
    };

    buttonContainer.appendChild(viewOnlineButton);
    buttonContainer.appendChild(exportExcelButton);
    buttonContainer.appendChild(closeButton);

    headerContainer.appendChild(buttonContainer);

    container.appendChild(headerContainer);

    const statsContainer = document.createElement('div');
    statsContainer.style = `
      margin-bottom: 10px;
      padding: 10px;
      background-color: #f9f9f9;
      border: 1px solid #ddd;
      border-radius: 5px;
    `;

    const totalCategories = Object.keys(categories).length;
    const totalItems = Object.values(categories).reduce((sum, items) => sum + items.length, 0);
    const emptyCategories = Object.entries(categories)
      .filter(([id, items]) => items.length === 0)
      .map(([id]) => categories[id][0]?.name || id)
      .length;

    const statsText = `
      <p style="font-size: 14px;">总分类数: ${totalCategories}</p>
      <p style="font-size: 14px;">已获取分类数: ${totalCategories - emptyCategories}</p>
      <p style="font-size: 14px;">未获取分类数: ${emptyCategories}</p>
      <p style="font-size: 14px;">已获取商品数: ${totalItems}</p>
    `;

    statsContainer.innerHTML = statsText;

    container.appendChild(statsContainer);

    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);

    container.appendChild(filterContainer);

    const table = document.createElement('table');
    table.style = 'width: 100%; border-collapse: collapse;';

    const thead = document.createElement('thead');
    thead.innerHTML = `
      <tr>
        <th style="border: 1px solid black; padding: 8px; font-size: 14px;">分类名称</th>
        <th style="border: 1px solid black; padding: 8px; font-size: 14px;">商品数量</th>
        <th style="border: 1px solid black; padding: 8px; font-size: 14px;">获取状态</th>
      </tr>
    `;

    const tbody = document.createElement('tbody');
    Object.entries(categories).forEach(([categoryName, items]) => {
      const status = items.length > 0 ? '已获取' : '未获取';
      const statusColor = {
        '已获取': '#4caf50',
        '未获取': '#f44336',
      }[status] || '#000';

      tbody.innerHTML += `
        <tr>
          <td style="border: 1px solid black; padding: 8px; font-size: 14px;">${categoryName}</td>
          <td style="border: 1px solid black; padding: 8px; text-align: right; font-size: 14px;">${items.length}</td>
          <td style="border: 1px solid black; padding: 8px; color: ${statusColor}; font-size: 14px;">${status}</td>
        </tr>
      `;
    });

    table.appendChild(thead);
    table.appendChild(tbody);

    container.appendChild(table);

    document.body.appendChild(container);

    const updateTable = (filterValue) => {
      const rows = tbody.getElementsByTagName('tr');
      Array.from(rows).forEach(row => {
        const statusCell = row.cells[2].innerText;
        if (filterValue === '全部' || statusCell === filterValue) {
          row.style.display = '';
        } else {
          row.style.display = 'none';
        }
      });
    };
  };

  const convertDataAndShow = () => {
    convertData();
    window.open('about:blank').document.write(`
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>商品信息</title>
        <style>
          body { font-family: Arial, sans-serif; }
          table { width: 100%; border-collapse: collapse; margin-top: 20px; }
          th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
          th { background-color: #f2f2f2; }
        </style>
      </head>
      <body>
        <h1>商品信息</h1>
        <pre>${JSON.stringify(food, null, 2)}</pre>
      </body>
      </html>
    `);
  };

  const convertData = () => {
    food = {
      data: {
        food_spu_tags: [],
        poi_info: {
          id: -100,
          poi_id_str: poiIdStr,
          name: shopInfo.name,
          bulletin: "恭喜发财666",
          pic_url: shopInfo.logo
        }
      }
    };

    // 添加分类信息
    Object.keys(categories).forEach((categoryName, index) => {
      const category = {
        category_code: null,
        tag: `${index + 1}`,
        name: categoryName,
        spus: []
      };

      categories[categoryName].forEach(item => {
        const spu = {
          id: item.id,
          name: item.name,
          min_price: item.min_price,
          picture: item.picture,
          attrs: item.attrs,
          sku_label: item.sku_label,
          skus: item.skus.map(sku => ({
            id: sku.id,
            spec: sku.spec
          }))
        };
        category.spus.push(spu);
      });

      food.data.food_spu_tags.push(category);
    });

    console.log('数据转换完成:', food);
  };

/*---------------------------------------------------------------------------------------------------------------------------------*/
const getShopInfo = () => {
  // 尝试通过新的结构获取店铺名称和logo
  const shopNameElementNew = document.querySelector('.shop-header-info-card__name.mor-comp-view');
  const shopLogoElementNew = document.querySelector('.shop-header-info-card__logo.mor-comp-view');

  // 尝试通过旧的结构获取店铺名称和logo
  const shopNameElementOld = document.getElementById('shic2-shop-name');
  const shopLogoElementOld = document.querySelector('.shic2-shop-logo');

  let shopName = '';
  let shopLogoUrl = '';

  if (shopNameElementNew && shopLogoElementNew) {
    shopName = shopNameElementNew.textContent.trim().replace(/&amp;/g, '&');
    shopLogoUrl = shopLogoElementNew.style.backgroundImage.replace(/url\(|\)|"/g, '') ;
  } else if (shopNameElementOld && shopLogoElementOld) {
    shopName = shopNameElementOld.textContent.trim().replace(/&amp;/g, '&');
    shopLogoUrl = shopLogoElementOld.getAttribute('src') || 'https://himg.bdimg.com/sys/portrait/item/pp.1.61637635.q_9U7gFy_biR3yojcvZygw.jpg?tt=1732025929684';
  } else {
    shopName = '未知店铺';
    shopLogoUrl = 'https://himg.bdimg.com/sys/portrait/item/pp.1.61637635.q_9U7gFy_biR3yojcvZygw.jpg?tt=1732025929684';
  }

  shopInfo = {
    name: shopName,
    logo: shopLogoUrl
  };
  console.log('门店信息获取完成:', shopInfo);
};

/*---------------------------------------------------------------------------------------------------------------------------------*/


  const getPoiIdStr = () => {
    const urlParams = new URLSearchParams(window.location.search);
    const traceId = urlParams.get('trace_id');
    if (traceId) {
      poiIdStr = traceId;
    }
    console.log('poi_id_str 获取完成:', poiIdStr);
  };

  const observePopup = () => {
    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.addedNodes.length > 0) {
          mutation.addedNodes.forEach(node => {
            if (node.classList && (node.classList.contains('sku__wrapper') || node.classList.contains('sku-wrapper'))) {
              const popupElement = node;
              const itemName = popupElement.querySelector('.sku--header-title, .sku-header .sku-header-title')?.textContent?.trim();
              const specGroups = popupElement.querySelectorAll('.sku--group, .sku-group');

              const specifications = [];
              specGroups.forEach(group => {
                const specTitle = group.querySelector('.sku--body_h2, .sku-group-title')?.textContent?.trim();
                const options = Array.from(group.querySelectorAll('.sku-option__root, .sku-group-item')).map(optionElement => {
                  const nameSelector = optionElement.querySelector('.ml-ellipsis.lh-32.font-24.color-19, .sku-group-item-title, .lh-30');
                  const priceSelector = optionElement.querySelector('.option-price, .sku-group-item-price');
                  return {
                    name: nameSelector?.textContent?.trim(),
                    price: priceSelector?.textContent || '+ ¥0'
                  };
                });
                specifications.push({
                  name: specTitle,
                  values: options.map(option => ({
                    id: option.name,  // 这里假设每个选项的名字是唯一的,可以用作ID
                    value: option.name
                  }))
                });
              });

              specificationsStorage[itemName] = specifications;  // 将规格信息存储在内存中
              console.log(`规格信息已保存到内存中,商品名称: ${itemName}, 规格信息:`, specifications);
            }
          });
        }
      });
    });

    observer.observe(document.body, { childList: true, subtree: true });
  };

  const observer = new MutationObserver(() => {
    if (window.location.href.includes('/2021001185671035/pages/ele-takeout-index/ele-takeout-index?')) {
      getCategoryData();
      getMenuItemData();
      getShopInfo();
      getPoiIdStr();
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });

  // 初始化获取一次分类数据
  if (window.location.href.includes('/2021001185671035/pages/ele-takeout-index/ele-takeout-index?')) {
    getCategoryData();
    getMenuItemData();
    getShopInfo();
    getPoiIdStr();
  }

  // 开始监听弹出页面
  observePopup();

  const exportToExcel = (foodData) => {
    if (!foodData || !foodData.data) {
      console.error('No valid food data provided to exportToExcel');
      return;
    }

    const workbook = XLSX.utils.book_new();
    const worksheetData = [
      ['店名', '店铺logo', '店铺分类', '商品名称', '详细商品规格信息', '商品图片', '价格信息']
    ];

    foodData.data.poi_info.name = foodData.data.poi_info.name.replace(/"/g, '""'); // 处理包含双引号的情况

    foodData.data.food_spu_tags.forEach(tag => {
      tag.spus.forEach(spu => {
        let specsInfo = '';
        spu.attrs.forEach(attr => {
          specsInfo += `${attr.name}: ${attr.values.map(val => val.value).join('/')}\n`;
        });
        specsInfo = specsInfo.trim().replace(/"/g, '""'); // 处理包含双引号的情况

        worksheetData.push([
          `"${foodData.data.poi_info.name}"`,
          foodData.data.poi_info.pic_url,
          tag.name,
          `"${spu.name}"`,
          specsInfo,
          spu.picture,
          spu.min_price
        ]);
      });
    });
    const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
    XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
    XLSX.writeFile(workbook, `${foodData.data.poi_info.name}_商品信息.xlsx`);
  };

  const openViewPage = (foodData) => {
    if (!foodData || !foodData.data) {
      console.error('No valid food data provided to openViewPage');
      return;
    }

    const htmlContent = `
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>商品信息</title>
        <style>
          body { font-family: Arial, sans-serif; }
          table { width: 100%; border-collapse: collapse; margin-top: 20px; }
          th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
          th { background-color: #f2f2f2; }
          img { max-width: 100px; height: auto; }
        </style>
      </head>
      <body>
      <div class="store-info">
          <h2>${foodData.data.poi_info.name}</h2>
          <img src="${foodData.data.poi_info.pic_url}" alt="店铺logo" style="max-width: 100px; height: auto;">
        </div>
        <table>
          <thead>
            <tr>
              <th>店名</th>
              <th>店铺分类</th>
              <th>商品名称</th>
              <th>详细商品规格信息</th>
              <th>商品图片</th>
              <th>价格信息</th>
            </tr>
          </thead>
          <tbody>
            ${foodData.data.food_spu_tags.map(tag =>
              tag.spus.map(spu => `
                <tr>
                  <td>${foodData.data.poi_info.name}</td>
                  <td>${tag.name}</td>
                  <td>${spu.name}</td>
                  <td>${spu.attrs.map(attr => `${attr.name}: ${attr.values.map(val => val.value).join('/')}`).join('<br>')}</td>
                  <td><img src="${spu.picture}" alt="${spu.name}"></td>
                  <td>${spu.min_price}</td>
                </tr>
              `).join('')
            ).join('')}
          </tbody>
        </table>
      </body>
      </html>
    `;

    const newWindow = window.open('', '_blank', 'width=800,height=600');
    newWindow.document.write(htmlContent);
    newWindow.document.close();
  };
})();