AI生成的美食食材配料生成器(源码)

效果预览

美食食材配料表生成器(内置版)

美食配料生成器(独立站版)

效果截图

QQ浏览器截图20250524132708

QQ浏览器截图20250524132740

QQ浏览器截图20250524132651

源码展示

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>美食食材配料表生成器</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
  
  <!-- 配置Tailwind自定义主题 -->
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            'meat-red': '#FF6B6B',
            'vegetable-green': '#4CD137',
            'seasoning-gold': '#F7CA18',
            'action-blue': '#3498DB',
            'light-gray': '#ECF0F1',
            'dark-text': '#2C3E50',
          },
          fontFamily: {
            sans: ['"Noto Sans SC"', 'sans-serif'],
          },
        },
      }
    }
  </script>
  
  <style type="text/tailwindcss">
    @layer utilities {
      .content-auto {
        content-visibility: auto;
      }
      .btn-hover {
        @apply transition-all duration-300 hover:scale-105 active:scale-95;
      }
      .card-hover {
        @apply transition-all duration-300 hover:shadow-lg hover:-translate-y-1;
      }
      .input-focus {
        @apply focus:ring-2 focus:ring-action-blue focus:border-action-blue;
      }
      .scrollbar-hide::-webkit-scrollbar {
        display: none;
      }
      .scrollbar-hide {
        -ms-overflow-style: none;
        scrollbar-width: none;
      }
      .dragging {
        @apply opacity-50 scale-95;
      }
      .placeholder-gray-400::placeholder {
        color: rgba(156, 163, 175, 0.5);
      }
    }
  </style>
</head>
<body class="bg-light-gray font-sans text-dark-text min-h-screen">
  <div id="app" class="container mx-auto px-4 py-6 max-w-5xl">
    <!-- 顶部栏 -->
    <header class="flex justify-between items-center mb-8">
      <h1 class="text-[clamp(1.5rem,3vw,2rem)] font-bold text-center flex-1">美食食材配料表生成器</h1>
    </header>
    
    <!-- 主界面内容 -->
    <main class="flex flex-col items-center justify-center min-h-[calc(100vh-200px)]">
      <!-- 已选食材计数 -->
      <div id="selection-count" class="w-full mb-6 bg-white p-4 rounded-lg shadow-md text-center">
        <span class="text-lg">已选:<span id="meat-count" class="text-meat-red font-bold">0</span> 肉类,
        <span id="vegetable-count" class="text-vegetable-green font-bold">0</span> 素菜,
        <span id="seasoning-count" class="text-seasoning-gold font-bold">0</span> 配料</span>
      </div>
      
      <!-- 核心按钮区 -->
      <div class="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-md mb-12">
        <button id="meat-btn" class="bg-meat-red text-white rounded-xl p-6 shadow-lg btn-hover flex flex-col items-center">
          <i class="fa fa-cutlery text-3xl mb-2"></i>
          <span class="text-xl font-bold">肉类</span>
        </button>
        
        <button id="vegetable-btn" class="bg-vegetable-green text-white rounded-xl p-6 shadow-lg btn-hover flex flex-col items-center">
          <i class="fa fa-leaf text-3xl mb-2"></i>
          <span class="text-xl font-bold">素菜</span>
        </button>
        
        <button id="seasoning-btn" class="bg-seasoning-gold text-white rounded-xl p-6 shadow-lg btn-hover flex flex-col items-center">
          <i class="fa fa-flask text-3xl mb-2"></i>
          <span class="text-xl font-bold">配料</span>
        </button>
      </div>
      
      <!-- 生成列表按钮 -->
      <button id="generate-btn" class="bg-gradient-to-r from-action-blue to-blue-600 text-white text-xl font-bold py-4 px-12 rounded-full shadow-lg btn-hover w-full max-w-md">
        生成配料表
      </button>
    </main>
    
    <!-- 生成的配料表区域 -->
    <div id="ingredients-list" class="hidden mt-8 bg-white rounded-lg shadow-lg p-6">
      <div class="flex justify-between items-center mb-4">
        <h2 class="text-xl font-bold">食材配料表</h2>
        <button id="reset-btn" class="text-red-500 hover:text-red-700 btn-hover">
          <i class="fa fa-refresh mr-2"></i>清空重置
        </button>
      </div>
      <div class="overflow-x-auto">
        <table class="w-full">
          <thead>
            <tr class="border-b">
              <th class="text-left py-2 px-4">分类</th>
              <th class="text-left py-2 px-4">食材名称</th>
              <th class="text-left py-2 px-4">数量</th>
              <th class="text-left py-2 px-4">排序</th>
              <th class="text-left py-2 px-4">操作</th>
            </tr>
          </thead>
          <tbody id="ingredients-table-body">
            <!-- 动态生成的表格内容 -->
          </tbody>
        </table>
      </div>
      
      <div class="flex justify-between mt-6">
        <button id="sort-btn" class="bg-light-gray text-dark-text py-2 px-4 rounded-lg shadow btn-hover">
          <i class="fa fa-sort mr-2"></i>一键排序
        </button>
        <button id="copy-btn" class="bg-action-blue text-white py-2 px-4 rounded-lg shadow btn-hover">
          <i class="fa fa-copy mr-2"></i>复制列表
        </button>
      </div>
    </div>
    
    <!-- 历史记录面板 -->
    <div id="history-section" class="mt-8 bg-white rounded-lg shadow-lg p-6">
      <h2 class="text-xl font-bold mb-4 flex justify-between items-center">
        历史记录
        <button id="clear-history-btn" class="text-sm text-red-500 hover:text-red-700 btn-hover">
          <i class="fa fa-trash mr-1"></i>清空历史
        </button>
      </h2>
      <div id="history-list" class="space-y-3 max-h-[300px] overflow-y-auto">
        <!-- 历史记录将动态生成 -->
      </div>
    </div>
    
    <!-- 肉类选择弹窗 -->
    <div id="meat-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
      <div class="bg-white rounded-t-lg rounded-b-2xl shadow-xl w-full max-w-md max-h-[80vh] flex flex-col">
        <div class="p-4 border-b flex justify-between items-center">
          <h3 class="text-xl font-bold">选择肉类</h3>
          <button id="close-meat-btn" class="text-gray-500 hover:text-dark-text">
            <i class="fa fa-times"></i>
          </button>
        </div>
        
        <div class="p-4">
          <div class="relative mb-4">
            <input type="text" id="meat-search" placeholder="搜索肉类..." class="w-full py-2 px-4 rounded-lg border shadow-sm input-focus">
            <i class="fa fa-search absolute right-4 top-3 text-gray-400"></i>
          </div>
          
          <div class="grid grid-cols-3 gap-3 overflow-y-auto max-h-[40vh] scrollbar-hide">
            <!-- 肉类选项将动态生成 -->
          </div>
        </div>
      </div>
    </div>
    
    <!-- 素菜选择弹窗 -->
    <div id="vegetable-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
      <div class="bg-white rounded-t-lg rounded-b-2xl shadow-xl w-full max-w-md max-h-[80vh] flex flex-col">
        <div class="p-4 border-b flex justify-between items-center">
          <h3 class="text-xl font-bold">选择素菜</h3>
          <button id="close-vegetable-btn" class="text-gray-500 hover:text-dark-text">
            <i class="fa fa-times"></i>
          </button>
        </div>
        
        <div class="p-4">
          <div class="relative mb-4">
            <input type="text" id="vegetable-search" placeholder="搜索素菜..." class="w-full py-2 px-4 rounded-lg border shadow-sm input-focus">
            <i class="fa fa-search absolute right-4 top-3 text-gray-400"></i>
          </div>
          
          <div class="grid grid-cols-3 gap-3 overflow-y-auto max-h-[40vh] scrollbar-hide">
            <!-- 素菜选项将动态生成 -->
          </div>
        </div>
      </div>
    </div>
    
    <!-- 配料选择弹窗 -->
    <div id="seasoning-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
      <div class="bg-white rounded-t-lg rounded-b-2xl shadow-xl w-full max-w-md max-h-[80vh] flex flex-col">
        <div class="p-4 border-b flex justify-between items-center">
          <h3 class="text-xl font-bold">选择配料</h3>
          <button id="close-seasoning-btn" class="text-gray-500 hover:text-dark-text">
            <i class="fa fa-times"></i>
          </button>
        </div>
        
        <div class="p-4">
          <!-- 新增搜索框 -->
          <div class="relative mb-4">
            <input type="text" id="seasoning-search" placeholder="搜索配料..." class="w-full py-2 px-4 rounded-lg border shadow-sm input-focus">
            <i class="fa fa-search absolute right-4 top-3 text-gray-400"></i>
          </div>
          
          <!-- 配料分类标签 -->
          <div class="flex mb-4 overflow-x-auto scrollbar-hide pb-2">
            <button class="seasoning-tab active bg-seasoning-gold text-white py-2 px-4 rounded-full mr-2 btn-hover whitespace-nowrap" data-category="基础配料">基础配料</button>
            <button class="seasoning-tab bg-light-gray text-dark-text py-2 px-4 rounded-full mr-2 btn-hover whitespace-nowrap" data-category="酱汁配料">酱汁配料</button>
            <button class="seasoning-tab bg-light-gray text-dark-text py-2 px-4 rounded-full btn-hover whitespace-nowrap" data-category="特殊配料">特殊配料</button>
          </div>
          
          <div class="grid grid-cols-2 gap-3 overflow-y-auto max-h-[30vh] scrollbar-hide" id="seasoning-items">
            <!-- 配料选项将动态生成 -->
          </div>
        </div>
      </div>
    </div>
    
    <!-- 数量选择弹窗 -->
    <div id="quantity-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
      <div class="bg-white rounded-xl shadow-xl w-full max-w-md p-6">
        <h3 class="text-xl font-bold mb-4">选择数量</h3>
        
        <div class="mb-6">
          <p class="text-lg mb-2">食材:<span id="quantity-food-name" class="font-bold"></span></p>
          
          <div class="flex items-center justify-between mb-4">
            <button id="decrease-btn" class="bg-light-gray text-dark-text w-12 h-12 rounded-full flex items-center justify-center btn-hover">
              <i class="fa fa-minus"></i>
            </button>
            
            <input type="number" id="quantity-input" min="1" class="text-2xl font-bold w-24 text-center border-b-2 border-gray-300 focus:outline-none focus:border-action-blue placeholder-gray-400" placeholder="100">
            
            <button id="increase-btn" class="bg-light-gray text-dark-text w-12 h-12 rounded-full flex items-center justify-center btn-hover">
              <i class="fa fa-plus"></i>
            </button>
          </div>
          
          <div class="grid grid-cols-4 gap-2">
            <button class="unit-btn active bg-action-blue text-white py-2 rounded-lg btn-hover" data-unit="克">克</button>
            <button class="unit-btn bg-light-gray text-dark-text py-2 rounded-lg btn-hover" data-unit="斤">斤</button>
            <button class="unit-btn bg-light-gray text-dark-text py-2 rounded-lg btn-hover" data-unit="片">片</button>
            <button class="unit-btn bg-light-gray text-dark-text py-2 rounded-lg btn-hover" data-unit="勺">勺</button>
          </div>
        </div>
        
        <div class="flex justify-between">
          <button id="cancel-quantity-btn" class="bg-light-gray text-dark-text py-3 px-6 rounded-lg shadow btn-hover">取消</button>
          <button id="confirm-quantity-btn" class="bg-action-blue text-white py-3 px-6 rounded-lg shadow btn-hover">确认</button>
        </div>
      </div>
    </div>
    
    <!-- 复制列表时的菜品名称弹窗 -->
    <div id="dish-name-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
      <div class="bg-white rounded-xl shadow-xl w-full max-w-md p-6">
        <h3 class="text-xl font-bold mb-4">请输入菜品名称</h3>
        
        <div class="mb-6">
          <input type="text" id="dish-name-input" placeholder="例如:宫保鸡丁" class="w-full py-3 px-4 rounded-lg border shadow-sm input-focus">
        </div>
        
        <div class="flex justify-between">
          <button id="cancel-dish-name-btn" class="bg-light-gray text-dark-text py-3 px-6 rounded-lg shadow btn-hover">取消</button>
          <button id="confirm-dish-name-btn" class="bg-action-blue text-white py-3 px-6 rounded-lg shadow btn-hover">确认</button>
        </div>
      </div>
    </div>
    
    <!-- 通知提示 -->
    <div id="notification" class="fixed top-4 right-4 bg-dark-text text-white p-4 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 z-50 flex items-center">
      <i class="fa fa-check-circle mr-2 text-action-blue"></i>
      <span id="notification-text">操作成功</span>
    </div>
  </div>

  <script>
    // 数据模型
    const state = {
      selectedFoods: [], // 已选择的食材
      currentFood: null, // 当前正在操作的食材
      currentCategory: '基础配料', // 当前配料分类
      history: JSON.parse(localStorage.getItem('foodGeneratorHistory')) || [] // 历史记录
    };
    
    // 食材数据 - 添加肉类对应符号表情
    const foodData = {
      meats: [
        { id: 'm01', name: '猪肉', icon: '🐷' },
        { id: 'm02', name: '腊肉', icon: '🥓' },
        { id: 'm03', name: '猪肝', icon: '🐷' },
        { id: 'm04', name: '鸡蛋', icon: '🥚' },
        { id: 'm05', name: '牛肉', icon: '🐂' },
        { id: 'm06', name: '鸭肉', icon: '🦆' },
        { id: 'm07', name: '鸡肉', icon: '🐔' },
        { id: 'm08', name: '鱼肉', icon: '🐟' },
        { id: 'm09', name: '排骨', icon: '🍖' },
        { id: 'm10', name: '五花肉', icon: '🐷' },
        { id: 'm11', name: '卤肉', icon: '🍖' },
        { id: 'm12', name: '扣肉', icon: '🍖' },
        { id: 'm13', name: '羊肉', icon: '🐑' },
        { id: 'm14', name: '火腿', icon: '🍖' },
        { id: 'm15', name: '肉肠', icon: '🌭' },
        { id: 'm16', name: '猪蹄', icon: '🐷' },
        { id: 'm17', name: '虾', icon: '🦐' },
        { id: 'm18', name: '鸡腿', icon: '🍗' },
        { id: 'm19', name: '鸭腿', icon: '🦆' },
        { id: 'm20', name: '鸡翅根', icon: '🍗' }
      ],
      
      vegetables: [
        { id: 'v01', name: '青椒', icon: 'fa-leaf' },
        { id: 'v02', name: '螺丝椒', icon: 'fa-fire' },
        { id: 'v03', name: '土豆', icon: 'fa-leaf' },
        { id: 'v04', name: '苦瓜', icon: 'fa-leaf' },
        { id: 'v05', name: '丝瓜', icon: 'fa-leaf' },
        { id: 'v06', name: '豆角', icon: 'fa-leaf' },
        { id: 'v07', name: '荷兰豆', icon: 'fa-leaf' },
        { id: 'v08', name: '毛豆', icon: 'fa-leaf' },
        { id: 'v09', name: '韭菜', icon: 'fa-leaf' },
        { id: 'v10', name: '西兰花', icon: 'fa-leaf' },
        { id: 'v11', name: '红椒', icon: 'fa-circle' },
        { id: 'v12', name: '灯笼椒', icon: 'fa-circle' },
        { id: 'v13', name: '黄瓜', icon: 'fa-leaf' },
        { id: 'v14', name: '南瓜', icon: 'fa-leaf' },
        { id: 'v15', name: '生菜', icon: 'fa-leaf' },
        { id: 'v16', name: '莲藕', icon: 'fa-leaf' },
        { id: 'v17', name: '包菜', icon: 'fa-leaf' },
        { id: 'v18', name: '娃娃菜', icon: 'fa-leaf' },
        { id: 'v19', name: '大白菜', icon: 'fa-leaf' },
        { id: 'v20', name: '小白菜', icon: 'fa-leaf' },
        { id: 'v21', name: '菠菜', icon: 'fa-leaf' },
        { id: 'v22', name: '油麦菜', icon: 'fa-leaf' },
        { id: 'v23', name: '空心菜', icon: 'fa-leaf' },
        { id: 'v24', name: '茼蒿', icon: 'fa-leaf' },
        { id: 'v25', name: '苋菜', icon: 'fa-leaf' },
        { id: 'v26', name: '芹菜', icon: 'fa-leaf' },
        { id: 'v27', name: '白菜', icon: 'fa-leaf' },
        { id: 'v28', name: '莴笋', icon: 'fa-leaf' },
        { id: 'v29', name: '竹笋', icon: 'fa-leaf' },
        { id: 'v30', name: '胡萝卜', icon: 'fa-fire' }
      ],
      
      seasonings: {
        '基础配料': [
          { id: 's01', name: '调和油', icon: 'fa-flask' },
          { id: 's02', name: '蒜米', icon: 'fa-flask' },
          { id: 's03', name: '酱汁', icon: 'fa-flask' },
          { id: 's04', name: '葱段', icon: 'fa-flask' },
          { id: 's05', name: '生粉水', icon: 'fa-flask' },
          { id: 's06', name: '过滤水', icon: 'fa-tint' },
          { id: 's07', name: '红美人椒', icon: 'fa-fire' },
          { id: 's08', name: '小炒辣椒', icon: 'fa-fire' },
          { id: 's09', name: '胡椒粉', icon: 'fa-flask' },
          { id: 's10', name: '番茄酱', icon: 'fa-flask' },
          { id: 's11', name: '姜片', icon: 'fa-flask' },
          { id: 's12', name: '小米辣', icon: 'fa-fire' },
          { id: 's13', name: '盐', icon: 'fa-flask' },
          { id: 's14', name: '糖', icon: 'fa-flask' },
          { id: 's15', name: '辣椒', icon: 'fa-fire' },
          { id: 's16', name: '花椒', icon: 'fa-flask' },
          { id: 's17', name: '八角', icon: 'fa-flask' },
          { id: 's18', name: '桂皮', icon: 'fa-flask' },
          { id: 's19', name: '豆豉', icon: 'fa-leaf' }
        ],
        '酱汁配料': [
          { id: 's20', name: '生抽', icon: 'fa-flask' },
          { id: 's21', name: '老抽', icon: 'fa-flask' },
          { id: 's22', name: '料酒', icon: 'fa-flask' },
          { id: 's23', name: '醋', icon: 'fa-flask' },
          { id: 's24', name: '蚝油', icon: 'fa-flask' },
          { id: 's25', name: '辣椒酱', icon: 'fa-fire' },
          { id: 's26', name: '甜面酱', icon: 'fa-flask' },
          { id: 's27', name: '淀粉', icon: 'fa-flask' }
        ],
        '特殊配料': [
          { id: 's28', name: '花椒粉', icon: 'fa-flask' },
          { id: 's29', name: '孜然粉', icon: 'fa-flask' },
          { id: 's30', name: '五香粉', icon: 'fa-flask' },
          { id: 's31', name: '咖喱粉', icon: 'fa-flask' },
          { id: 's32', name: '老干妈', icon: 'fa-fire' },
          { id: 's33', name: '豆瓣酱', icon: 'fa-flask' }
        ]
      }
    };
    
    // DOM元素
    const elements = {
      // 主界面
      meatBtn: document.getElementById('meat-btn'),
      vegetableBtn: document.getElementById('vegetable-btn'),
      seasoningBtn: document.getElementById('seasoning-btn'),
      generateBtn: document.getElementById('generate-btn'),
      ingredientsList: document.getElementById('ingredients-list'),
      ingredientsTableBody: document.getElementById('ingredients-table-body'),
      meatCount: document.getElementById('meat-count'),
      vegetableCount: document.getElementById('vegetable-count'),
      seasoningCount: document.getElementById('seasoning-count'),
      sortBtn: document.getElementById('sort-btn'),
      copyBtn: document.getElementById('copy-btn'),
      resetBtn: document.getElementById('reset-btn'),
      
      // 历史记录
      historyList: document.getElementById('history-list'),
      clearHistoryBtn: document.getElementById('clear-history-btn'),
      
      // 肉类弹窗
      meatModal: document.getElementById('meat-modal'),
      closeMeatBtn: document.getElementById('close-meat-btn'),
      meatSearch: document.getElementById('meat-search'),
      
      // 素菜弹窗
      vegetableModal: document.getElementById('vegetable-modal'),
      closeVegetableBtn: document.getElementById('close-vegetable-btn'),
      vegetableSearch: document.getElementById('vegetable-search'),
      
      // 配料弹窗
      seasoningModal: document.getElementById('seasoning-modal'),
      seasoningTabs: document.querySelectorAll('.seasoning-tab'),
      closeSeasoningBtn: document.getElementById('close-seasoning-btn'),
      seasoningSearch: document.getElementById('seasoning-search'),
      
      // 数量弹窗
      quantityModal: document.getElementById('quantity-modal'),
      closeQuantityBtn: document.getElementById('cancel-quantity-btn'),
      confirmQuantityBtn: document.getElementById('confirm-quantity-btn'),
      quantityFoodName: document.getElementById('quantity-food-name'),
      quantityInput: document.getElementById('quantity-input'),
      decreaseBtn: document.getElementById('decrease-btn'),
      increaseBtn: document.getElementById('increase-btn'),
      unitBtns: document.querySelectorAll('.unit-btn'),
      
      // 菜品名称弹窗
      dishNameModal: document.getElementById('dish-name-modal'),
      dishNameInput: document.getElementById('dish-name-input'),
      cancelDishNameBtn: document.getElementById('cancel-dish-name-btn'),
      confirmDishNameBtn: document.getElementById('confirm-dish-name-btn'),
      
      // 通知
      notification: document.getElementById('notification'),
      notificationText: document.getElementById('notification-text')
    };
    
    // 初始化应用
    function initApp() {
      // 渲染肉类选项
      renderFoodOptions('meats', foodData.meats);
      
      // 渲染素菜选项
      renderFoodOptions('vegetables', foodData.vegetables);
      
      // 渲染配料选项
      renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
      
      // 初始化事件监听器
      initEventListeners();
      
      // 更新计数
      updateSelectionCount();
      
      // 渲染历史记录
      renderHistory();
      
      // 初始化拖拽排序
      initDragSort();
    }
    
    // 初始化事件监听器
    function initEventListeners() {
      // 主界面按钮
      elements.meatBtn.addEventListener('click', () => showModal('meat'));
      elements.vegetableBtn.addEventListener('click', () => showModal('vegetable'));
      elements.seasoningBtn.addEventListener('click', () => showModal('seasoning'));
      elements.generateBtn.addEventListener('click', generateIngredientsList);
      elements.sortBtn.addEventListener('click', sortIngredients);
      elements.copyBtn.addEventListener('click', () => {
        if (state.selectedFoods.length === 0) {
          showNotification('请先生成配料表', 'error');
          return;
        }
        elements.dishNameModal.classList.remove('hidden');
      });
      elements.resetBtn.addEventListener('click', resetAll);
      
      // 历史记录面板
      elements.clearHistoryBtn.addEventListener('click', clearHistory);
      
      // 肉类弹窗
      elements.closeMeatBtn.addEventListener('click', () => elements.meatModal.classList.add('hidden'));
      elements.meatSearch.addEventListener('input', filterMeats);
      
      // 素菜弹窗
      elements.closeVegetableBtn.addEventListener('click', () => elements.vegetableModal.classList.add('hidden'));
      elements.vegetableSearch.addEventListener('input', filterVegetables);
      
      // 配料弹窗
      elements.closeSeasoningBtn.addEventListener('click', () => elements.seasoningModal.classList.add('hidden'));
      elements.seasoningSearch.addEventListener('input', filterSeasonings);
      
      // 配料分类标签
      elements.seasoningTabs.forEach(tab => {
        tab.addEventListener('click', () => {
          // 切换标签样式
          elements.seasoningTabs.forEach(t => {
            t.classList.remove('active', 'bg-seasoning-gold', 'text-white');
            t.classList.add('bg-light-gray', 'text-dark-text');
          });
          tab.classList.add('active', 'bg-seasoning-gold', 'text-white');
          tab.classList.remove('bg-light-gray', 'text-dark-text');
          
          // 更新当前分类并渲染配料
          state.currentCategory = tab.dataset.category;
          renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
          
          // 清空搜索框
          elements.seasoningSearch.value = '';
        });
      });
      
      // 数量弹窗
      elements.closeQuantityBtn.addEventListener('click', () => elements.quantityModal.classList.add('hidden'));
      elements.confirmQuantityBtn.addEventListener('click', confirmQuantity);
      elements.decreaseBtn.addEventListener('click', decreaseQuantity);
      elements.increaseBtn.addEventListener('click', increaseQuantity);
      
      // 单位按钮
      elements.unitBtns.forEach(btn => {
        btn.addEventListener('click', () => {
          elements.unitBtns.forEach(b => {
            b.classList.remove('active', 'bg-action-blue', 'text-white');
            b.classList.add('bg-light-gray', 'text-dark-text');
          });
          btn.classList.add('active', 'bg-action-blue', 'text-white');
          btn.classList.remove('bg-light-gray', 'text-dark-text');
        });
      });
      
      // 菜品名称弹窗
      elements.cancelDishNameBtn.addEventListener('click', () => {
        elements.dishNameModal.classList.add('hidden');
      });
      
      elements.confirmDishNameBtn.addEventListener('click', async () => {
        const dishName = elements.dishNameInput.value.trim();
        if (!dishName) {
          showNotification('请输入菜品名称', 'error');
          return;
        }
        
        try {
          await copyIngredientsList(dishName);
          elements.dishNameModal.classList.add('hidden');
          elements.dishNameInput.value = '';
        } catch (err) {
          showNotification('复制失败,请重试(请允许剪贴板权限)', 'error');
          console.error('复制失败:', err);
        }
      });
      
      // ESC键关闭弹窗
      document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
          ['meat-modal', 'vegetable-modal', 'seasoning-modal', 'quantity-modal', 'dish-name-modal'].forEach(modalId => {
            document.getElementById(modalId)?.classList.add('hidden');
          });
        }
      });
    }
    
    // 初始化拖拽排序
    function initDragSort() {
      new Sortable(elements.ingredientsTableBody, {
        handle: '.drag-handle',
        animation: 150,
        ghostClass: 'bg-light-gray',
        onEnd: function(evt) {
          // 更新数据顺序
          const rows = Array.from(elements.ingredientsTableBody.querySelectorAll('tr'));
          const newOrder = rows.map(row => {
            return state.selectedFoods.find(food => food.id === row.dataset.id);
          });
          
          state.selectedFoods = newOrder;
          showNotification('排序已更新');
        }
      });
    }
    
    // 渲染食物选项
    function renderFoodOptions(type, foods) {
      const container = type === 'meats' 
        ? elements.meatModal.querySelector('.grid') 
        : elements.vegetableModal.querySelector('.grid');
      
      container.innerHTML = '';
      
      foods.forEach(food => {
        const isSelected = state.selectedFoods.some(item => item.id === food.id);
        
        const card = document.createElement('div');
        card.className = `card-hover rounded-lg shadow p-3 flex flex-col items-center relative cursor-pointer transition-all duration-300 ${
          isSelected 
            ? type === 'meats' 
              ? 'bg-meat-red/10 border-2 border-meat-red' 
              : 'bg-vegetable-green/10 border-2 border-vegetable-green' 
            : 'bg-white'
        }`;
        card.dataset.id = food.id;
        card.dataset.name = food.name;
        card.dataset.type = type === 'meats' ? '肉类' : '素菜';
        
        // 检查是否是肉类并使用表情符号
        const iconHtml = type === 'meats' 
          ? `<span class="text-2xl mb-2">${food.icon}</span>` 
          : `<i class="fa ${food.icon} text-2xl mb-2 ${type === 'meats' ? 'text-meat-red' : 'text-vegetable-green'}"></i>`;
        
        card.innerHTML += `
          ${iconHtml}
          <span class="text-center text-sm">${food.name}</span>
        `;
        
        card.addEventListener('click', () => {
          if (isSelected) {
            // 取消选择
            const index = state.selectedFoods.findIndex(item => item.id === food.id);
            if (index !== -1) {
              state.selectedFoods.splice(index, 1);
              updateSelectionCount();
              renderFoodOptions('meats', foodData.meats);
              renderFoodOptions('vegetables', foodData.vegetables);
              renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
              showNotification(`${food.name} 已从配料表中移除`);
            }
          } else {
            // 添加选择
            state.currentFood = {
              id: food.id,
              name: food.name,
              type: type === 'meats' ? '肉类' : '素菜',
              quantity: 100,
              unit: '克'
            };
            
            elements.quantityFoodName.textContent = food.name;
            elements.quantityInput.value = ''; // 设置为空
            elements.unitBtns[0].click(); // 默认选择"克"
            elements.quantityModal.classList.remove('hidden');
          }
        });
        
        container.appendChild(card);
      });
    }
    
    // 渲染配料选项
    function renderSeasoningOptions(seasonings) {
      const container = document.getElementById('seasoning-items');
      container.innerHTML = '';
      
      seasonings.forEach(seasoning => {
        const isSelected = state.selectedFoods.some(item => item.id === seasoning.id);
        
        const card = document.createElement('div');
        card.className = `card-hover rounded-lg shadow p-3 flex items-center justify-between cursor-pointer transition-all duration-300 ${
          isSelected 
            ? 'bg-seasoning-gold/10 border-2 border-seasoning-gold' 
            : 'bg-white'
        }`;
        card.dataset.id = seasoning.id;
        card.dataset.name = seasoning.name;
        card.dataset.type = '配料';
        
        card.innerHTML += `
          <div class="flex items-center">
            <i class="fa ${seasoning.icon} text-xl mr-2 text-seasoning-gold"></i>
            <span>${seasoning.name}</span>
          </div>
          ${isSelected ? '<i class="fa fa-check text-seasoning-gold"></i>' : ''}
        `;
        
        card.addEventListener('click', () => {
          if (isSelected) {
            // 取消选择
            const index = state.selectedFoods.findIndex(item => item.id === seasoning.id);
            if (index !== -1) {
              state.selectedFoods.splice(index, 1);
              updateSelectionCount();
              renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
              renderFoodOptions('meats', foodData.meats);
              renderFoodOptions('vegetables', foodData.vegetables);
              showNotification(`${seasoning.name} 已从配料表中移除`);
            }
          } else {
            // 添加选择
            state.currentFood = {
              id: seasoning.id,
              name: seasoning.name,
              type: '配料',
              quantity: 10,
              unit: '克'
            };
            
            elements.quantityFoodName.textContent = seasoning.name;
            elements.quantityInput.value = ''; // 设置为空
            elements.unitBtns[0].click(); // 默认选择"克"
            elements.quantityModal.classList.remove('hidden');
          }
        });
        
        container.appendChild(card);
      });
    }
    
    // 过滤肉类
    function filterMeats() {
      const searchTerm = elements.meatSearch.value.toLowerCase();
      const filteredMeats = foodData.meats.filter(meat => 
        meat.name.toLowerCase().includes(searchTerm)
      );
      renderFoodOptions('meats', filteredMeats);
    }
    
    // 过滤素菜
    function filterVegetables() {
      const searchTerm = elements.vegetableSearch.value.toLowerCase();
      const filteredVegetables = foodData.vegetables.filter(vegetable => 
        vegetable.name.toLowerCase().includes(searchTerm)
      );
      renderFoodOptions('vegetables', filteredVegetables);
    }
    
    // 过滤配料
    function filterSeasonings() {
      const searchTerm = elements.seasoningSearch.value.toLowerCase();
      const filteredSeasonings = foodData.seasonings[state.currentCategory].filter(seasoning => 
        seasoning.name.toLowerCase().includes(searchTerm)
      );
      renderSeasoningOptions(filteredSeasonings);
    }
    
    // 显示弹窗
    function showModal(type) {
      if (type === 'meat') {
        elements.meatModal.classList.remove('hidden');
        elements.meatSearch.focus();
      } else if (type === 'vegetable') {
        elements.vegetableModal.classList.remove('hidden');
        elements.vegetableSearch.focus();
      } else if (type === 'seasoning') {
        elements.seasoningModal.classList.remove('hidden');
        elements.seasoningSearch.focus();
      }
    }
    
    // 确认数量
    function confirmQuantity() {
      if (!state.currentFood) return;
      
      // 获取输入值,如果为空则使用默认值
      const inputValue = elements.quantityInput.value.trim();
      state.currentFood.quantity = inputValue ? parseInt(inputValue) : (state.currentFood.type === '配料' ? 10 : 100);
      
      // 获取选中的单位
      const activeUnitBtn = Array.from(elements.unitBtns).find(btn => 
        btn.classList.contains('active')
      );
      state.currentFood.unit = activeUnitBtn ? activeUnitBtn.dataset.unit : '克';
      
      // 检查是否已选择相同食材
      const existingIndex = state.selectedFoods.findIndex(
        item => item.id === state.currentFood.id
      );
      
      if (existingIndex !== -1) {
        // 更新已选择的食材
        state.selectedFoods[existingIndex] = state.currentFood;
      } else {
        // 添加新选择的食材
        state.selectedFoods.push(state.currentFood);
      }
      
      // 关闭弹窗
      elements.quantityModal.classList.add('hidden');
      
      // 更新UI
      updateSelectionCount();
      renderFoodOptions('meats', foodData.meats);
      renderFoodOptions('vegetables', foodData.vegetables);
      renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
      
      showNotification(`${state.currentFood.name} 已添加到配料表`);
      
      // 重置当前食材
      state.currentFood = null;
    }
    
    // 减少数量
    function decreaseQuantity() {
      const currentValue = elements.quantityInput.value.trim() 
        ? parseInt(elements.quantityInput.value) 
        : (state.currentFood.type === '配料' ? 10 : 100);
      
      if (currentValue > 1) {
        elements.quantityInput.value = currentValue - 1;
      }
    }
    
    // 增加数量
    function increaseQuantity() {
      const currentValue = elements.quantityInput.value.trim() 
        ? parseInt(elements.quantityInput.value) 
        : (state.currentFood.type === '配料' ? 10 : 100);
      
      elements.quantityInput.value = currentValue + 1;
    }
    
    // 更新选择计数
    function updateSelectionCount() {
      const meatCount = state.selectedFoods.filter(food => food.type === '肉类').length;
      const vegetableCount = state.selectedFoods.filter(food => food.type === '素菜').length;
      const seasoningCount = state.selectedFoods.filter(food => food.type === '配料').length;
      
      elements.meatCount.textContent = meatCount;
      elements.vegetableCount.textContent = vegetableCount;
      elements.seasoningCount.textContent = seasoningCount;
      
      // 控制生成按钮状态
      if (meatCount + vegetableCount + seasoningCount > 0) {
        elements.generateBtn.removeAttribute('disabled');
        elements.generateBtn.classList.remove('opacity-50', 'cursor-not-allowed');
      } else {
        elements.generateBtn.setAttribute('disabled', 'true');
        elements.generateBtn.classList.add('opacity-50', 'cursor-not-allowed');
      }
    }
    
    // 生成配料表
    function generateIngredientsList() {
      if (state.selectedFoods.length === 0) {
        showNotification('请先选择食材', 'error');
        return;
      }
      
      // 按类别排序:肉类 > 素菜 > 配料
      state.selectedFoods.sort((a, b) => {
        const categoryOrder = { '肉类': 1, '素菜': 2, '配料': 3 };
        return categoryOrder[a.type] - categoryOrder[b.type];
      });
      
      // 渲染配料表
      elements.ingredientsTableBody.innerHTML = '';
      
      state.selectedFoods.forEach(food => {
        const row = document.createElement('tr');
        row.className = 'border-b hover:bg-light-gray/50 transition-colors duration-200';
        row.dataset.id = food.id;
        
        const categoryClass = 
          food.type === '肉类' ? 'text-meat-red' : 
          food.type === '素菜' ? 'text-vegetable-green' : 'text-seasoning-gold';
        
        // 为肉类添加表情符号
        const nameWithIcon = food.type === '肉类' 
          ? `<span class="mr-1">${foodData.meats.find(m => m.id === food.id)?.icon || ''}</span>${food.name}` 
          : food.name;
        
        row.innerHTML = `
          <td class="py-3 px-4 ${categoryClass}">${food.type}</td>
          <td class="py-3 px-4">${nameWithIcon}</td>
          <td class="py-3 px-4">${food.quantity}${food.unit}</td>
          <td class="py-3 px-4">
            <button class="drag-handle bg-light-gray p-1 rounded cursor-move hover:bg-gray-200">
              <i class="fa fa-bars"></i>
            </button>
          </td>
          <td class="py-3 px-4">
            <button class="delete-btn text-red-500 hover:text-red-700 transition-colors duration-200" data-id="${food.id}">
              <i class="fa fa-trash"></i>
            </button>
          </td>
        `;
        
        elements.ingredientsTableBody.appendChild(row);
      });
      
      // 显示配料表
      elements.ingredientsList.classList.remove('hidden');
      
      // 滚动到配料表
      elements.ingredientsList.scrollIntoView({ behavior: 'smooth' });
      
      // 添加删除按钮事件
      document.querySelectorAll('.delete-btn').forEach(btn => {
        btn.addEventListener('click', (e) => {
          const foodId = e.currentTarget.dataset.id;
          const foodIndex = state.selectedFoods.findIndex(food => food.id === foodId);
          
          if (foodIndex !== -1) {
            const foodName = state.selectedFoods[foodIndex].name;
            state.selectedFoods.splice(foodIndex, 1);
            
            // 更新UI
            updateSelectionCount();
            renderFoodOptions('meats', foodData.meats);
            renderFoodOptions('vegetables', foodData.vegetables);
            renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
            generateIngredientsList();
            
            showNotification(`${foodName} 已从配料表中移除`);
          }
        });
      });
    }
    
    // 一键排序
    function sortIngredients() {
      if (state.selectedFoods.length === 0) return;
      
      // 按类别和名称排序
      state.selectedFoods.sort((a, b) => {
        const categoryOrder = { '肉类': 1, '素菜': 2, '配料': 3 };
        const categoryDiff = categoryOrder[a.type] - categoryOrder[b.type];
        
        if (categoryDiff !== 0) {
          return categoryDiff;
        }
        
        return a.name.localeCompare(b.name);
      });
      
      // 重新生成配料表
      generateIngredientsList();
      showNotification('配料表已按类别和名称排序');
    }
    
    // 复制配料表
    async function copyIngredientsList(dishName) {
      if (state.selectedFoods.length === 0) return;
      
      // 构建复制内容
      let content = `${dishName} 食材配料表\n\n`;
      
      // 按类别分组
      const groupedFoods = {
        '肉类': [],
        '素菜': [],
        '配料': []
      };
      
      state.selectedFoods.forEach(food => {
        groupedFoods[food.type].push(food);
      });
      
      // 按类别添加到内容
      Object.keys(groupedFoods).forEach(category => {
        if (groupedFoods[category].length > 0) {
          content += `${category}:\n`;
          groupedFoods[category].forEach(food => {
            // 不复制表情符号,只复制名称
            content += `- ${food.name} ${food.quantity}${food.unit}\n`;
          });
          content += '\n';
        }
      });
      
      try {
        // 复制到剪贴板
        await navigator.clipboard.writeText(content);
        
        // 保存到历史记录
        const historyItem = {
          id: Date.now(),
          name: dishName,
          ingredients: state.selectedFoods,
          timestamp: new Date().toLocaleString()
        };
        
        state.history.unshift(historyItem);
        
        // 限制历史记录数量为10
        if (state.history.length > 10) {
          state.history.pop();
        }
        
        // 保存到本地存储
        localStorage.setItem('foodGeneratorHistory', JSON.stringify(state.history));
        
        // 渲染历史记录
        renderHistory();
        
        showNotification(`"${dishName}" 的配料表已复制到剪贴板`);
      } catch (err) {
        console.error('复制失败:', err);
        throw err; // 抛出错误,由调用者处理
      }
    }
    
    // 渲染历史记录
    function renderHistory() {
      elements.historyList.innerHTML = '';
      
      if (state.history.length === 0) {
        elements.historyList.innerHTML = '<p class="text-center text-gray-500">暂无历史记录</p>';
        return;
      }
      
      state.history.forEach(item => {
        const historyCard = document.createElement('div');
        historyCard.className = 'bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow duration-300';
        
        historyCard.innerHTML = `
          <div class="flex justify-between items-start mb-2">
            <h4 class="font-bold text-lg">${item.name}</h4>
            <span class="text-sm text-gray-500">${item.timestamp}</span>
          </div>
          <div class="text-sm text-gray-600 mb-3">
            ${item.ingredients.length} 种食材 · 
            ${item.ingredients.filter(i => i.type === '肉类').length} 肉类 · 
            ${item.ingredients.filter(i => i.type === '素菜').length} 素菜 · 
            ${item.ingredients.filter(i => i.type === '配料').length} 配料
          </div>
          <div class="flex justify-between">
            <button class="load-btn text-action-blue hover:text-blue-700 transition-colors duration-200 text-sm" data-id="${item.id}">
              <i class="fa fa-refresh mr-1"></i>加载
            </button>
            <button class="delete-history-btn text-red-500 hover:text-red-700 transition-colors duration-200 text-sm" data-id="${item.id}">
              <i class="fa fa-trash mr-1"></i>删除
            </button>
          </div>
        </div>
        `;
        
        elements.historyList.appendChild(historyCard);
      });
      
      // 添加历史记录按钮事件
      document.querySelectorAll('.load-btn').forEach(btn => {
        btn.addEventListener('click', (e) => {
          const itemId = parseInt(e.currentTarget.dataset.id);
          const historyItem = state.history.find(item => item.id === itemId);
          
          if (historyItem) {
            state.selectedFoods = historyItem.ingredients;
            updateSelectionCount();
            renderFoodOptions('meats', foodData.meats);
            renderFoodOptions('vegetables', foodData.vegetables);
            renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
            generateIngredientsList();
            
            showNotification(`已加载"${historyItem.name}"的配料表`);
          }
        });
      });
      
      document.querySelectorAll('.delete-history-btn').forEach(btn => {
        btn.addEventListener('click', (e) => {
          const itemId = parseInt(e.currentTarget.dataset.id);
          const itemIndex = state.history.findIndex(item => item.id === itemId);
          
          if (itemIndex !== -1) {
            const itemName = state.history[itemIndex].name;
            state.history.splice(itemIndex, 1);
            
            // 保存到本地存储
            localStorage.setItem('foodGeneratorHistory', JSON.stringify(state.history));
            
            // 重新渲染
            renderHistory();
            
            showNotification(`已删除"${itemName}"的历史记录`);
          }
        });
      });
    }
    
    // 清空历史记录
    function clearHistory() {
      if (state.history.length === 0) {
        showNotification('历史记录已为空', 'info');
        return;
      }
      
      if (confirm('确定要清空所有历史记录吗?')) {
        state.history = [];
        localStorage.setItem('foodGeneratorHistory', JSON.stringify(state.history));
        renderHistory();
        showNotification('历史记录已清空');
      }
    }
    
    // 重置所有
    function resetAll() {
      if (state.selectedFoods.length === 0) {
        showNotification('配料表已为空', 'info');
        return;
      }
      
      if (confirm('确定要清空所有已选食材吗?')) {
        state.selectedFoods = [];
        updateSelectionCount();
        renderFoodOptions('meats', foodData.meats);
        renderFoodOptions('vegetables', foodData.vegetables);
        renderSeasoningOptions(foodData.seasonings[state.currentCategory]);
        elements.ingredientsList.classList.add('hidden');
        showNotification('已清空所有已选食材');
      }
    }
    
    // 显示通知
    function showNotification(message, type = 'success') {
      elements.notificationText.textContent = message;
      
      // 设置通知类型
      if (type === 'error') {
        elements.notification.classList.remove('bg-dark-text');
        elements.notification.classList.add('bg-red-600');
        elements.notification.querySelector('i').className = 'fa fa-exclamation-circle mr-2 text-white';
      } else if (type === 'info') {
        elements.notification.classList.remove('bg-dark-text');
        elements.notification.classList.add('bg-action-blue');
        elements.notification.querySelector('i').className = 'fa fa-info-circle mr-2 text-white';
      } else {
        elements.notification.classList.remove('bg-red-600', 'bg-action-blue');
        elements.notification.classList.add('bg-dark-text');
        elements.notification.querySelector('i').className = 'fa fa-check-circle mr-2 text-action-blue';
      }
      
      // 显示通知
      elements.notification.classList.remove('translate-x-full');
      
      // 3秒后隐藏通知
      setTimeout(() => {
        elements.notification.classList.add('translate-x-full');
      }, 3000);
    }
    
    // 初始化应用
    document.addEventListener('DOMContentLoaded', initApp);
  </script>
</body>
</html>

闲言碎语

本次使用豆包AI生成由Trae CN 源码

本来第一版本是长这样的(直接生成)

image

后面是先让他生成一个设计框架

image

image

然后再复制设计框架给它生成源码

image

然后就不断给它出修改建议

image

image

image

© 版权声明
THE END
喜欢就支持一下吧
点赞11赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容