PUGE 2 周之前
父節點
當前提交
0f55f9e0cb
共有 5 個文件被更改,包括 1080 次插入457 次删除
  1. 3 1
      .gitignore
  2. 295 211
      templates/index.html
  3. 0 0
      templates_excel/wayfair模板.xlsx
  4. 二進制
      templates_excel/亚马逊模板.xlsx
  5. 782 245
      web.py

+ 3 - 1
.gitignore

@@ -1,2 +1,4 @@
 build
-dist
+dist
+output
+uploads

+ 295 - 211
templates/index.html

@@ -5,11 +5,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Excel 自动填表工具</title>
     <style>
-        * {
-            margin: 0;
-            padding: 0;
-            box-sizing: border-box;
-        }
+        * { margin: 0; padding: 0; box-sizing: border-box; }
         body {
             font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
             background: #f5f7fa;
@@ -38,6 +34,11 @@
             margin-bottom: 24px;
             border-bottom: 1px solid #eee;
             padding-bottom: 16px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 10px;
         }
         .form-group {
             margin-bottom: 18px;
@@ -61,9 +62,9 @@
         }
         .form-row .form-group {
             flex: 1;
-            min-width: 180px;
+            min-width: 200px;
         }
-        select, input[type="file"], input[type="number"] {
+        select, input[type="file"] {
             width: 100%;
             padding: 10px 12px;
             border: 1px solid #d1d5db;
@@ -77,14 +78,48 @@
             outline: none;
             box-shadow: 0 0 0 3px rgba(79, 140, 247, 0.15);
         }
+        .template-options {
+            display: flex;
+            gap: 12px;
+            flex-wrap: wrap;
+        }
+        .template-option {
+            flex: 1;
+            min-width: 120px;
+            padding: 14px 20px;
+            border: 2px solid #e5e7eb;
+            border-radius: 10px;
+            cursor: pointer;
+            text-align: center;
+            font-weight: 600;
+            font-size: 15px;
+            transition: all 0.2s;
+            background: #fafbfc;
+        }
+        .template-option:hover {
+            border-color: #4f8cf7;
+            background: #f0f4ff;
+        }
+        .template-option.active {
+            border-color: #4f8cf7;
+            background: #e8effe;
+            box-shadow: 0 0 0 3px rgba(79, 140, 247, 0.2);
+        }
+        .template-option .badge {
+            display: block;
+            font-size: 11px;
+            font-weight: 400;
+            color: #888;
+            margin-top: 4px;
+        }
         .file-upload-wrapper {
-            position: relative;
             display: flex;
             align-items: center;
             gap: 12px;
         }
         .file-upload-wrapper input[type="file"] {
             padding: 8px;
+            flex: 1;
         }
         .file-name {
             font-size: 13px;
@@ -106,44 +141,16 @@
             align-items: center;
             gap: 8px;
         }
-        .btn-primary {
-            background: #4f8cf7;
-            color: #fff;
-        }
-        .btn-primary:hover {
-            background: #3a7ae8;
-            transform: translateY(-1px);
-        }
-        .btn-primary:disabled {
-            background: #b0c8f5;
-            cursor: not-allowed;
-            transform: none;
-        }
-        .btn-success {
-            background: #34c759;
-            color: #fff;
-        }
-        .btn-success:hover {
-            background: #2db84e;
-        }
-        .btn-success:disabled {
-            background: #9de0b2;
-            cursor: not-allowed;
-        }
-        .btn-secondary {
-            background: #e8ecf1;
-            color: #444;
-        }
-        .btn-secondary:hover {
-            background: #d5dbe3;
-        }
-        .btn-danger {
-            background: #ef4444;
-            color: #fff;
-        }
-        .btn-danger:hover {
-            background: #dc2626;
-        }
+        .btn-primary { background: #4f8cf7; color: #fff; }
+        .btn-primary:hover { background: #3a7ae8; transform: translateY(-1px); }
+        .btn-primary:disabled { background: #b0c8f5; cursor: not-allowed; transform: none; }
+        .btn-success { background: #34c759; color: #fff; }
+        .btn-success:hover { background: #2db84e; }
+        .btn-success:disabled { background: #9de0b2; cursor: not-allowed; }
+        .btn-secondary { background: #e8ecf1; color: #444; }
+        .btn-secondary:hover { background: #d5dbe3; }
+        .btn-danger { background: #ef4444; color: #fff; }
+        .btn-danger:hover { background: #dc2626; }
         .actions {
             display: flex;
             gap: 12px;
@@ -167,18 +174,10 @@
             line-height: 1.6;
             margin-top: 16px;
         }
-        .log-area .log-success {
-            color: #4ade80;
-        }
-        .log-area .log-error {
-            color: #f87171;
-        }
-        .log-area .log-warn {
-            color: #fbbf24;
-        }
-        .log-area .log-info {
-            color: #60a5fa;
-        }
+        .log-area .log-success { color: #4ade80; }
+        .log-area .log-error { color: #f87171; }
+        .log-area .log-warn { color: #fbbf24; }
+        .log-area .log-info { color: #60a5fa; }
         .preview-table-wrapper {
             overflow-x: auto;
             margin-top: 16px;
@@ -210,9 +209,7 @@
             text-overflow: ellipsis;
             white-space: nowrap;
         }
-        .preview-table tr:hover td {
-            background: #f9fafb;
-        }
+        .preview-table tr:hover td { background: #f9fafb; }
         .status-badge {
             display: inline-block;
             padding: 4px 14px;
@@ -220,41 +217,17 @@
             font-size: 13px;
             font-weight: 500;
         }
-        .status-badge.idle {
-            background: #e5e7eb;
-            color: #6b7280;
-        }
-        .status-badge.loading {
-            background: #fef3c7;
-            color: #d97706;
-        }
-        .status-badge.success {
-            background: #d1fae5;
-            color: #065f46;
-        }
-        .status-badge.error {
-            background: #fee2e2;
-            color: #991b1b;
-        }
-        .flex {
-            display: flex;
-            align-items: center;
-            gap: 12px;
-        }
-        .flex-between {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            flex-wrap: wrap;
-            gap: 10px;
-        }
+        .status-badge.idle { background: #e5e7eb; color: #6b7280; }
+        .status-badge.loading { background: #fef3c7; color: #d97706; }
+        .status-badge.success { background: #d1fae5; color: #065f46; }
+        .status-badge.error { background: #fee2e2; color: #991b1b; }
+        .flex { display: flex; align-items: center; gap: 12px; }
+        .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
         .mt-8 { margin-top: 8px; }
         .mt-12 { margin-top: 12px; }
-        .text-center { text-align: center; }
         .text-sm { font-size: 13px; }
         .text-muted { color: #888; }
         .hidden { display: none !important; }
-        
         .download-section {
             background: #ecfdf5;
             border: 1px solid #6ee7b7;
@@ -267,69 +240,98 @@
             gap: 12px;
             margin-top: 12px;
         }
-        .download-section.show {
-            display: flex;
+        .download-section.show { display: flex; }
+        .config-info {
+            font-size: 13px;
+            color: #666;
+            background: #f3f4f6;
+            padding: 6px 14px;
+            border-radius: 6px;
+            display: inline-block;
+        }
+        .config-info span { font-weight: 600; }
+        #aiLogContainer {
+            margin-top: 12px;
+        }
+        #aiLogArea {
+            background: #1a1a2e;
+            color: #e4e4e4;
+            border-radius: 8px;
+            padding: 12px 16px;
+            font-family: 'Courier New', monospace;
+            font-size: 12px;
+            max-height: 200px;
+            overflow-y: auto;
+            white-space: pre-wrap;
+            word-break: break-all;
+            line-height: 1.6;
         }
+        #aiLogArea .log-success { color: #4ade80; }
+        #aiLogArea .log-error { color: #f87171; }
+        #aiLogArea .log-warn { color: #fbbf24; }
+        #aiLogArea .log-info { color: #60a5fa; }
     </style>
 </head>
 <body>
 <div class="container">
     <h1>📊 Excel 自动填表工具</h1>
     <div class="subtitle">
-        选择模板 → 上传来源数据 → 预览 → 生成并下载
+        <span>选择模板 → 上传来源数据 → 预览 → 生成并下载</span>
         <span class="status-badge idle" id="statusBadge">就绪</span>
     </div>
 
-    <!-- 配置区域 -->
-    <div class="form-row">
-        <div class="form-group">
-            <label>📁 选择模板 <span class="hint">(模板文件需放在服务器 templates_excel/ 目录)</span></label>
-            <select id="templateSelect">
-                <option value="">-- 请选择模板 --</option>
-                {% for t in templates %}
-                <option value="{{ t }}">{{ t }}</option>
-                {% endfor %}
-            </select>
-        </div>
-        <div class="form-group">
-            <label>📄 来源数据 Excel</label>
-            <div class="file-upload-wrapper">
-                <input type="file" id="sourceFile" accept=".xlsx,.xls">
-                <span class="file-name" id="fileName">未选择文件</span>
+    <!-- 模板选择 -->
+    <div class="form-group">
+        <label>📁 选择平台模板</label>
+        <div class="template-options" id="templateOptions">
+            <div class="template-option active" data-key="wayfair" onclick="selectTemplate('wayfair')">
+                🏢 Wayfair
+                <span class="badge">wayfair模板.xlsx</span>
             </div>
+            <div class="template-option" data-key="amazon" onclick="selectTemplate('amazon')">
+                🛒 亚马逊
+                <span class="badge">亚马逊模板.xlsx</span>
+            </div>
+        </div>
+        <div class="mt-8" id="configInfo">
+            <span class="config-info">✅ 已选择 <span>Wayfair</span> 模板 | 来源表头: 第 2 行 | 模板表头: 第 4 行</span>
         </div>
     </div>
 
-    <div class="form-row">
-        <div class="form-group">
-            <label>来源表头行号 <span class="hint">(第2行是字段名)</span></label>
-            <input type="number" id="sourceHeader" value="2" min="1">
-        </div>
-        <div class="form-group">
-            <label>模板表头行号 <span class="hint">(第4行是字段名)</span></label>
-            <input type="number" id="templateHeader" value="4" min="1">
+    <!-- 文件上传 -->
+    <div class="form-group">
+        <label>📄 来源数据 Excel</label>
+        <div class="file-upload-wrapper">
+            <input type="file" id="sourceFile" accept=".xlsx,.xls">
+            <span class="file-name" id="fileName">未选择文件</span>
         </div>
     </div>
+    <div class="form-group" style="margin-top: 12px;">
+        <label style="display:flex;align-items:center;gap:10px;cursor:pointer;">
+            <input type="checkbox" id="useAI" checked>
+            🤖 启用 AI 自动填写(识别产品主图,自动填充属性)
+            <span class="hint">(需要产品主图URL)</span>
+        </label>
+    </div>
 
     <!-- 操作按钮 -->
     <div class="actions">
-        <button class="btn btn-primary" id="previewBtn">🔍 预览数据</button>
+        <button class="btn btn-primary" id="previewBtn" disabled>🔍 预览数据</button>
         <button class="btn btn-success" id="generateBtn" disabled>🚀 生成并下载</button>
         <button class="btn btn-secondary" id="clearLogBtn">🗑️ 清空日志</button>
     </div>
 
-    <!-- 状态信息 -->
     <div class="flex-between">
         <span id="statusText" class="text-muted text-sm">请选择模板和来源文件</span>
         <span id="rowCount" class="text-muted text-sm"></span>
     </div>
 
-    <!-- 预览表格 -->
+    <!-- 预览 -->
     <div id="previewContainer" class="hidden">
         <div class="flex-between mt-8">
             <span class="text-sm" style="font-weight:600;">📋 数据预览 (前10行)</span>
         </div>
-        <div class="preview-table-wrapper" id="previewWrapper">
+        <div class="preview-table-wrapper">
             <table class="preview-table" id="previewTable">
                 <thead id="previewHead"></thead>
                 <tbody id="previewBody"></tbody>
@@ -337,26 +339,31 @@
         </div>
     </div>
 
-    <!-- 下载区域 -->
+    <!-- 下载 -->
     <div class="download-section" id="downloadSection">
         <span style="font-weight:600;color:#065f46;">✅ 生成完成!</span>
         <span id="downloadInfo"></span>
         <a href="#" id="downloadLink" class="btn btn-success" style="text-decoration:none;">📥 下载文件</a>
     </div>
-
+    <div id="aiLogContainer" class="hidden">
+        <div class="flex-between mt-12">
+            <span style="font-weight:600;font-size:14px;">🤖 AI 处理日志</span>
+            <span id="aiProgress" class="text-muted text-sm"></span>
+        </div>
+        <div class="log-area" id="aiLogArea" style="max-height:200px;font-size:11px;margin-top:8px;">
+            <span class="log-info">💡 AI 日志将在这里显示...</span>
+        </div>
+    </div>
     <!-- 日志 -->
     <div class="log-area" id="logArea">
-        <span class="log-info">💡 系统就绪,等待操作...</span>
+        <span class="log-info">💡 系统就绪,请选择模板并上传来源文件</span>
     </div>
 </div>
 
 <script>
-    // DOM 元素
-    const templateSelect = document.getElementById('templateSelect');
+    const templateOptions = document.querySelectorAll('.template-option');
     const sourceFile = document.getElementById('sourceFile');
     const fileName = document.getElementById('fileName');
-    const sourceHeader = document.getElementById('sourceHeader');
-    const templateHeader = document.getElementById('templateHeader');
     const previewBtn = document.getElementById('previewBtn');
     const generateBtn = document.getElementById('generateBtn');
     const clearLogBtn = document.getElementById('clearLogBtn');
@@ -370,77 +377,89 @@
     const downloadSection = document.getElementById('downloadSection');
     const downloadLink = document.getElementById('downloadLink');
     const downloadInfo = document.getElementById('downloadInfo');
+    const configInfo = document.getElementById('configInfo');
 
+    let selectedTemplate = 'wayfair';
     let sourcePath = null;
 
-    // 日志函数
-    function log(msg, type = 'info') {
-        const colors = {
-            info: 'log-info',
-            success: 'log-success',
-            error: 'log-error',
-            warn: 'log-warn'
-        };
-        const prefix = {
-            info: 'ℹ️',
-            success: '✅',
-            error: '❌',
-            warn: '⚠️'
-        };
-        const timestamp = new Date().toLocaleTimeString();
-        const line = document.createElement('div');
-        line.innerHTML = `<span style="color:#888;">[${timestamp}]</span> <span class="${colors[type] || 'log-info'}">${prefix[type] || 'ℹ️'} ${msg}</span>`;
-        logArea.appendChild(line);
-        logArea.scrollTop = logArea.scrollHeight;
-    }
+    const templateNames = { wayfair: 'Wayfair', amazon: '亚马逊' };
 
-    function setStatus(text, type = 'idle') {
-        statusText.textContent = text;
-        statusBadge.textContent = {
-            idle: '就绪',
-            loading: '处理中...',
-            success: '成功',
-            error: '失败'
-        }[type] || '就绪';
-        statusBadge.className = `status-badge ${type}`;
-    }
-
-    function clearLog() {
-        logArea.innerHTML = '';
-        log('日志已清空', 'info');
+    function selectTemplate(key) {
+        selectedTemplate = key;
+        templateOptions.forEach(el => {
+            el.classList.toggle('active', el.dataset.key === key);
+        });
+        
+        fetch('/get_config')
+            .then(res => res.json())
+            .then(data => {
+                const config = data.config[key];
+                if (config) {
+                    configInfo.innerHTML = `
+                        <span class="config-info">
+                            ✅ 已选择 <span>${templateNames[key]}</span> 模板
+                            | 来源表头: 第 ${config.source_header} 行
+                            | 模板表头: 第 ${config.template_header} 行
+                            | 文件: ${config.file}
+                        </span>
+                    `;
+                }
+            });
+        
+        checkReady();
+        log(`📁 选择模板: ${templateNames[key] || key}`, 'info');
     }
 
-    // 文件选择
     sourceFile.addEventListener('change', function() {
         if (this.files.length > 0) {
             fileName.textContent = this.files[0].name;
+            log(`📄 选择文件: ${this.files[0].name}`, 'info');
         } else {
             fileName.textContent = '未选择文件';
         }
         checkReady();
     });
 
-    templateSelect.addEventListener('change', checkReady);
-
     function checkReady() {
-        const hasTemplate = templateSelect.value !== '';
+        const hasTemplate = selectedTemplate !== null;
         const hasFile = sourceFile.files.length > 0;
         previewBtn.disabled = !(hasTemplate && hasFile);
         generateBtn.disabled = true;
         if (hasTemplate && hasFile) {
             setStatus('已选择模板和来源文件,点击预览', 'idle');
+        } else if (!hasTemplate) {
+            setStatus('请选择模板', 'idle');
         } else {
-            setStatus('请选择模板和来源文件', 'idle');
+            setStatus('请选择来源文件', 'idle');
         }
     }
 
-    // 预览
+    function log(msg, type = 'info') {
+        const colors = { info: 'log-info', success: 'log-success', error: 'log-error', warn: 'log-warn' };
+        const prefix = { info: 'ℹ️', success: '✅', error: '❌', warn: '⚠️' };
+        const timestamp = new Date().toLocaleTimeString();
+        const line = document.createElement('div');
+        line.innerHTML = `<span style="color:#888;">[${timestamp}]</span> <span class="${colors[type] || 'log-info'}">${prefix[type] || 'ℹ️'} ${msg}</span>`;
+        logArea.appendChild(line);
+        logArea.scrollTop = logArea.scrollHeight;
+    }
+
+    function setStatus(text, type = 'idle') {
+        statusText.textContent = text;
+        const labels = { idle: '就绪', loading: '处理中...', success: '成功', error: '失败' };
+        statusBadge.textContent = labels[type] || '就绪';
+        statusBadge.className = `status-badge ${type}`;
+    }
+
+    function clearLog() {
+        logArea.innerHTML = '';
+        log('日志已清空', 'info');
+    }
+    clearLogBtn.addEventListener('click', clearLog);
+
     previewBtn.addEventListener('click', async function() {
-        const template = templateSelect.value;
         const file = sourceFile.files[0];
-        const sHeader = parseInt(sourceHeader.value) || 2;
-
-        if (!template || !file) {
+        if (!selectedTemplate || !file) {
             log('请选择模板和来源文件', 'warn');
             return;
         }
@@ -451,14 +470,10 @@
 
         const formData = new FormData();
         formData.append('source_file', file);
-        formData.append('template_name', template);
-        formData.append('source_header', sHeader);
+        formData.append('template_key', selectedTemplate);
 
         try {
-            const response = await fetch('/preview', {
-                method: 'POST',
-                body: formData
-            });
+            const response = await fetch('/preview', { method: 'POST', body: formData });
             const result = await response.json();
 
             if (result.error) {
@@ -473,11 +488,12 @@
             setStatus(`预览成功,共 ${result.total_rows} 行`, 'success');
             rowCount.textContent = `共 ${result.total_rows} 行`;
 
-            // 渲染表格
-            renderPreview(result.columns, result.data);
+            previewHead.innerHTML = `<tr>${result.columns.map(c => `<th>${escapeHtml(c)}</th>`).join('')}</tr>`;
+            previewBody.innerHTML = result.data.map(row => {
+                return `<tr>${result.columns.map(c => `<td title="${escapeHtml(String(row[c] || ''))}">${escapeHtml(String(row[c] || ''))}</td>`).join('')}</tr>`;
+            }).join('');
+            
             previewContainer.classList.remove('hidden');
-
-            // 启用生成按钮
             generateBtn.disabled = false;
 
         } catch (err) {
@@ -488,66 +504,137 @@
         }
     });
 
-    function renderPreview(columns, data) {
-        // 表头
-        previewHead.innerHTML = `<tr>${columns.map(c => `<th>${escapeHtml(c)}</th>`).join('')}</tr>`;
-        
-        // 数据行
-        previewBody.innerHTML = data.map(row => {
-            return `<tr>${columns.map(c => `<td title="${escapeHtml(String(row[c] || ''))}">${escapeHtml(String(row[c] || ''))}</td>`).join('')}</tr>`;
-        }).join('');
-    }
-
     function escapeHtml(text) {
         const div = document.createElement('div');
         div.textContent = text;
         return div.innerHTML;
     }
 
-    // 生成
     generateBtn.addEventListener('click', async function() {
         if (!sourcePath) {
             log('请先预览数据', 'warn');
             return;
         }
 
-        const template = templateSelect.value;
-        const sHeader = parseInt(sourceHeader.value) || 2;
-        const tHeader = parseInt(templateHeader.value) || 4;
-
         this.disabled = true;
         previewBtn.disabled = true;
         setStatus('正在生成...', 'loading');
         log('🚀 开始生成并追加到模板...', 'info');
 
+        // 显示AI日志区域
+        const aiLogContainer = document.getElementById('aiLogContainer');
+        if (aiLogContainer) {
+            aiLogContainer.classList.remove('hidden');
+            document.getElementById('aiLogArea').innerHTML = '<span class="log-info">⏳ AI处理中,请等待...</span>';
+        }
+
         const formData = new FormData();
         formData.append('source_path', sourcePath);
-        formData.append('template_name', template);
-        formData.append('source_header', sHeader);
-        formData.append('header_row', tHeader);
+        formData.append('template_key', selectedTemplate);
+        const useAICheckbox = document.getElementById('useAI');
+        formData.append('use_ai', useAICheckbox ? useAICheckbox.checked : false);
+
+        // 开始轮询AI日志
+        let logInterval = null;
+        let pollCount = 0;
+        const maxPolls = 300; // 最多轮询300次(10分钟)
+
+        function startAILogPolling() {
+            if (logInterval) clearInterval(logInterval);
+            
+            logInterval = setInterval(async function() {
+                pollCount++;
+                try {
+                    const response = await fetch('/get_ai_logs');
+                    const data = await response.json();
+                    if (data.logs && data.logs.length > 0) {
+                        updateAILogDisplay(data.logs);
+                    }
+                    // 如果轮询超过最大次数,自动停止
+                    if (pollCount >= maxPolls) {
+                        clearInterval(logInterval);
+                        logInterval = null;
+                    }
+                } catch (e) {
+                    // 静默失败,不中断轮询
+                }
+            }, 2000);  // 每2秒刷新一次
+        }
+
+        function updateAILogDisplay(logs) {
+            const aiLogArea = document.getElementById('aiLogArea');
+            if (!aiLogArea) return;
+            
+            // 只显示最近的日志(避免太多)
+            const recentLogs = logs.slice(-50);
+            aiLogArea.innerHTML = recentLogs.map(log => {
+                const colors = {
+                    info: 'log-info',
+                    success: 'log-success',
+                    error: 'log-error',
+                    warn: 'log-warn'
+                };
+                const prefix = {
+                    info: 'ℹ️',
+                    success: '✅',
+                    error: '❌',
+                    warn: '⚠️'
+                };
+                const level = log.level || 'info';
+                return `<div><span style="color:#888;">[${log.time}]</span> <span class="${colors[level] || 'log-info'}">${prefix[level] || 'ℹ️'} ${log.message}</span></div>`;
+            }).join('');
+            aiLogArea.scrollTop = aiLogArea.scrollHeight;
+        }
+
+        // 开始轮询
+        startAILogPolling();
 
         try {
-            const response = await fetch('/generate', {
-                method: 'POST',
-                body: formData
+            const response = await fetch('/generate', { 
+                method: 'POST', 
+                body: formData 
             });
             const result = await response.json();
 
+            // 停止轮询
+            if (logInterval) {
+                clearInterval(logInterval);
+                logInterval = null;
+            }
+
             if (result.error) {
                 log(`❌ 生成失败: ${result.error}`, 'error');
                 setStatus('生成失败', 'error');
+                // 显示错误日志
+                if (result.logs) {
+                    updateAILogDisplay(result.logs);
+                }
                 return;
             }
 
             log(`✅ ${result.message}`, 'success');
             setStatus(`生成成功,追加 ${result.filled_count} 行`, 'success');
 
-            // 显示下载
             downloadSection.classList.add('show');
             downloadLink.href = result.download_url;
             downloadInfo.textContent = `已追加 ${result.filled_count} 行数据`;
 
+            // 显示最终AI日志
+            if (result.logs) {
+                updateAILogDisplay(result.logs);
+            }
+
+            // 如果AI处理了数据,显示成功信息
+            if (result.ai_used && result.ai_rows > 0) {
+                document.getElementById('aiProgress').textContent = `✅ AI识别完成,成功 ${result.ai_rows} 行`;
+            }
+
         } catch (err) {
+            // 停止轮询
+            if (logInterval) {
+                clearInterval(logInterval);
+                logInterval = null;
+            }
             log(`❌ 请求失败: ${err.message}`, 'error');
             setStatus('生成失败', 'error');
         } finally {
@@ -556,12 +643,9 @@
         }
     });
 
-    // 清空日志
-    clearLogBtn.addEventListener('click', clearLog);
-
-    // 初始检查
-    checkReady();
-    log('💡 请选择模板和来源Excel文件,然后点击"预览数据"', 'info');
+    // 默认选中 Wayfair
+    selectTemplate('wayfair');
+    log('💡 请上传来源Excel文件,然后点击"预览数据"', 'info');
 </script>
 </body>
 </html>

+ 0 - 0
templates_excel/模板.xlsx → templates_excel/wayfair模板.xlsx


二進制
templates_excel/亚马逊模板.xlsx


File diff suppressed because it is too large
+ 782 - 245
web.py


Some files were not shown because too many files changed in this diff