PUGE 1 ay önce
ebeveyn
işleme
da857b51ed
2 değiştirilmiş dosya ile 757 ekleme ve 366 silme
  1. 206 172
      templates/index.html
  2. 551 194
      web.py

+ 206 - 172
templates/index.html

@@ -13,7 +13,7 @@
             padding: 20px;
         }
         .container {
-            max-width: 1100px;
+            max-width: 1200px;
             margin: 0 auto;
             background: #fff;
             border-radius: 12px;
@@ -116,10 +116,12 @@
             display: flex;
             align-items: center;
             gap: 12px;
+            flex-wrap: wrap;
         }
         .file-upload-wrapper input[type="file"] {
             padding: 8px;
             flex: 1;
+            min-width: 150px;
         }
         .file-name {
             font-size: 13px;
@@ -140,6 +142,7 @@
             display: inline-flex;
             align-items: center;
             gap: 8px;
+            text-decoration: none;
         }
         .btn-primary { background: #4f8cf7; color: #fff; }
         .btn-primary:hover { background: #3a7ae8; transform: translateY(-1px); }
@@ -149,8 +152,11 @@
         .btn-success:disabled { background: #9de0b2; cursor: not-allowed; }
         .btn-secondary { background: #e8ecf1; color: #444; }
         .btn-secondary:hover { background: #d5dbe3; }
+        .btn-warning { background: #f59e0b; color: #fff; }
+        .btn-warning:hover { background: #d97706; }
         .btn-danger { background: #ef4444; color: #fff; }
         .btn-danger:hover { background: #dc2626; }
+        .btn-sm { padding: 6px 16px; font-size: 13px; }
         .actions {
             display: flex;
             gap: 12px;
@@ -167,7 +173,7 @@
             padding: 16px 20px;
             font-family: 'Courier New', monospace;
             font-size: 12px;
-            max-height: 320px;
+            max-height: 500px;
             overflow-y: auto;
             white-space: pre-wrap;
             word-break: break-all;
@@ -250,71 +256,124 @@
             display: inline-block;
         }
         .config-info span { font-weight: 600; }
-        #aiLogContainer {
-            margin-top: 12px;
+        .example-download {
+            margin-top: 8px;
+            display: flex;
+            gap: 12px;
+            flex-wrap: wrap;
+            align-items: center;
+        }
+        .example-download .btn { font-size: 12px; padding: 5px 14px; }
+        #detailLogContainer {
+            margin-top: 16px;
+            border: 2px solid #e5e7eb;
+            border-radius: 10px;
+            padding: 16px;
+            background: #fafbfc;
+        }
+        #detailLogContainer .log-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 10px;
+        }
+        #detailLogContainer .log-header span {
+            font-weight: 700;
+            font-size: 15px;
+            color: #1a1a2e;
         }
-        #aiLogArea {
+        #detailLogArea {
             background: #1a1a2e;
             color: #e4e4e4;
             border-radius: 8px;
-            padding: 12px 16px;
+            padding: 14px 18px;
             font-family: 'Courier New', monospace;
             font-size: 12px;
-            max-height: 200px;
+            max-height: 500px;
+            min-height: 120px;
             overflow-y: auto;
             white-space: pre-wrap;
             word-break: break-all;
-            line-height: 1.6;
+            line-height: 1.7;
+        }
+        #detailLogArea .log-success { color: #4ade80; }
+        #detailLogArea .log-error { color: #f87171; }
+        #detailLogArea .log-warn { color: #fbbf24; }
+        #detailLogArea .log-info { color: #60a5fa; }
+        .checkbox-wrapper {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            padding: 8px 0;
+        }
+        .checkbox-wrapper input[type="checkbox"] {
+            width: 18px;
+            height: 18px;
+            cursor: pointer;
+        }
+        .checkbox-wrapper label {
+            font-weight: 600;
+            font-size: 14px;
+            cursor: pointer;
+        }
+        .log-count {
+            font-size: 12px;
+            color: #666;
+            background: #e5e7eb;
+            padding: 2px 10px;
+            border-radius: 12px;
         }
-        #aiLogArea .log-success { color: #4ade80; }
-        #aiLogArea .log-error { color: #f87171; }
-        #aiLogArea .log-warn { color: #fbbf24; }
-        #aiLogArea .log-info { color: #60a5fa; }
     </style>
+<base target="_blank">
 </head>
 <body>
 <div class="container">
     <h1>📊 Excel 自动填表工具</h1>
     <div class="subtitle">
-        <span>选择模板 → 上传来源数据 → 预览 → 生成并下载</span>
+        <span>选择模板类型 → 上传模板文件 → 上传数据文件 → 生成</span>
         <span class="status-badge idle" id="statusBadge">就绪</span>
     </div>
 
-    <!-- 模板选择 -->
     <div class="form-group">
-        <label>📁 选择平台模板</label>
+        <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>
+                <span class="badge">表头: 第4行</span>
             </div>
             <div class="template-option" data-key="amazon" onclick="selectTemplate('amazon')">
                 🛒 亚马逊
-                <span class="badge">亚马逊模板.xlsx</span>
+                <span class="badge">表头: 第1行</span>
             </div>
         </div>
+
         <div class="mt-8" id="configInfo">
-            <span class="config-info">✅ 已选择 <span>Wayfair</span> 模板 | 来源表头: 第 2 行 | 模板表头: 第 4 行</span>
+            <span class="config-info">✅ 已选择 <span>Wayfair</span> | 来源表头: 第 2 行 | 模板表头: 第 4 行</span>
         </div>
     </div>
 
-    <!-- 文件上传 -->
     <div class="form-group">
-        <label>📄 来源数据 Excel</label>
+        <label>📄 上传模板文件 <span class="hint">(.xlsx 格式)</span></label>
         <div class="file-upload-wrapper">
-            <input type="file" id="sourceFile" accept=".xlsx,.xls">
-            <span class="file-name" id="fileName">未选择文件</span>
+            <input type="file" id="templateFile" accept=".xlsx,.xls,.xlsm">
+            <span class="file-name" id="templateFileName">未选择文件</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 class="form-group">
+        <label>📊 上传数据文件 <span class="hint">(.xlsx 格式)</span></label>
+        <div class="file-upload-wrapper">
+            <input type="file" id="dataFile" accept=".xlsx,.xls,.xlsm">
+            <span class="file-name" id="dataFileName">未选择文件</span>
+        </div>
+    </div>
+
+    <div class="checkbox-wrapper">
+        <input type="checkbox" id="useAI" checked>
+        <label for="useAI">🤖 启用 AI 自动填写(识别产品主图,自动填充属性)</label>
+        <span class="hint">(需要数据中有产品主图URL)</span>
     </div>
 
-    <!-- 操作按钮 -->
     <div class="actions">
         <button class="btn btn-primary" id="previewBtn" disabled>🔍 预览数据</button>
         <button class="btn btn-success" id="generateBtn" disabled>🚀 生成并下载</button>
@@ -322,11 +381,10 @@
     </div>
 
     <div class="flex-between">
-        <span id="statusText" class="text-muted text-sm">请选择模板和来源文件</span>
+        <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>
@@ -339,31 +397,35 @@
         </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 id="detailLogContainer" class="hidden">
+        <div class="log-header">
+            <span>📋 详细处理日志</span>
+            <span class="log-count" id="logCount">0 条</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 id="detailLogArea">
+            <span class="log-info">⏳ 等待处理开始...</span>
         </div>
     </div>
-    <!-- 日志 -->
+
+    <!-- 主日志 -->
     <div class="log-area" id="logArea">
-        <span class="log-info">💡 系统就绪,请选择模板并上传来源文件</span>
+        <span class="log-info">💡 系统就绪,请选择模板类型,上传模板和数据文件,然后点击"预览数据"</span>
     </div>
 </div>
 
 <script>
     const templateOptions = document.querySelectorAll('.template-option');
-    const sourceFile = document.getElementById('sourceFile');
-    const fileName = document.getElementById('fileName');
+    const templateFile = document.getElementById('templateFile');
+    const dataFile = document.getElementById('dataFile');
+    const templateFileName = document.getElementById('templateFileName');
+    const dataFileName = document.getElementById('dataFileName');
     const previewBtn = document.getElementById('previewBtn');
     const generateBtn = document.getElementById('generateBtn');
     const clearLogBtn = document.getElementById('clearLogBtn');
@@ -378,10 +440,14 @@
     const downloadLink = document.getElementById('downloadLink');
     const downloadInfo = document.getElementById('downloadInfo');
     const configInfo = document.getElementById('configInfo');
+    const useAI = document.getElementById('useAI');
+    const detailLogContainer = document.getElementById('detailLogContainer');
+    const detailLogArea = document.getElementById('detailLogArea');
+    const logCount = document.getElementById('logCount');
 
     let selectedTemplate = 'wayfair';
-    let sourcePath = null;
-
+    let dataPath = null;
+    let logInterval = null;
     const templateNames = { wayfair: 'Wayfair', amazon: '亚马逊' };
 
     function selectTemplate(key) {
@@ -389,7 +455,6 @@
         templateOptions.forEach(el => {
             el.classList.toggle('active', el.dataset.key === key);
         });
-        
         fetch('/get_config')
             .then(res => res.json())
             .then(data => {
@@ -397,40 +462,53 @@
                 if (config) {
                     configInfo.innerHTML = `
                         <span class="config-info">
-                            ✅ 已选择 <span>${templateNames[key]}</span> 模板
+                            ✅ 已选择 <span>${templateNames[key]}</span>
                             | 来源表头: 第 ${config.source_header} 行
                             | 模板表头: 第 ${config.template_header} 行
-                            | 文件: ${config.file}
                         </span>
                     `;
                 }
             });
-        
         checkReady();
-        log(`📁 选择模板: ${templateNames[key] || key}`, 'info');
+        log(`📁 选择模板类型: ${templateNames[key] || key}`, 'info');
     }
 
-    sourceFile.addEventListener('change', function() {
+
+
+    templateFile.addEventListener('change', function() {
+        if (this.files.length > 0) {
+            templateFileName.textContent = this.files[0].name;
+            log(`📄 选择模板文件: ${this.files[0].name}`, 'info');
+        } else {
+            templateFileName.textContent = '未选择文件';
+        }
+        checkReady();
+    });
+
+    dataFile.addEventListener('change', function() {
         if (this.files.length > 0) {
-            fileName.textContent = this.files[0].name;
-            log(`📄 选择文件: ${this.files[0].name}`, 'info');
+            dataFileName.textContent = this.files[0].name;
+            log(`📊 选择数据文件: ${this.files[0].name}`, 'info');
         } else {
-            fileName.textContent = '未选择文件';
+            dataFileName.textContent = '未选择文件';
         }
         checkReady();
     });
 
     function checkReady() {
         const hasTemplate = selectedTemplate !== null;
-        const hasFile = sourceFile.files.length > 0;
-        previewBtn.disabled = !(hasTemplate && hasFile);
+        const hasTemplateFile = templateFile.files.length > 0;
+        const hasDataFile = dataFile.files.length > 0;
+        previewBtn.disabled = !(hasTemplate && hasTemplateFile && hasDataFile);
         generateBtn.disabled = true;
-        if (hasTemplate && hasFile) {
-            setStatus('已选择模板和来源文件,点击预览', 'idle');
+        if (hasTemplate && hasTemplateFile && hasDataFile) {
+            setStatus('已选择模板类型,已上传模板和数据文件,点击预览', 'idle');
         } else if (!hasTemplate) {
-            setStatus('请选择模板', 'idle');
-        } else {
-            setStatus('请选择来源文件', 'idle');
+            setStatus('请选择模板类型', 'idle');
+        } else if (!hasTemplateFile) {
+            setStatus('请上传模板文件', 'idle');
+        } else if (!hasDataFile) {
+            setStatus('请上传数据文件', 'idle');
         }
     }
 
@@ -453,49 +531,86 @@
 
     function clearLog() {
         logArea.innerHTML = '';
+        detailLogArea.innerHTML = '<span class="log-info">💡 日志已清空</span>';
+        logCount.textContent = '0 条';
         log('日志已清空', 'info');
     }
     clearLogBtn.addEventListener('click', clearLog);
 
+    function updateDetailLogDisplay(logs) {
+        if (!detailLogArea) return;
+        const recentLogs = logs.slice(-100);
+        detailLogArea.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('');
+        detailLogArea.scrollTop = detailLogArea.scrollHeight;
+        logCount.textContent = `${logs.length} 条`;
+    }
+
+    function startLogPolling() {
+        if (logInterval) clearInterval(logInterval);
+        detailLogContainer.classList.remove('hidden');
+        detailLogArea.innerHTML = '<span class="log-info">⏳ 处理中,正在获取详细日志...</span>';
+        let pollCount = 0;
+        const maxPolls = 600;
+        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) {
+                    updateDetailLogDisplay(data.logs);
+                }
+                if (pollCount >= maxPolls) {
+                    clearInterval(logInterval);
+                    logInterval = null;
+                }
+            } catch (e) {}
+        }, 1200);
+    }
+
+    function stopLogPolling() {
+        if (logInterval) {
+            clearInterval(logInterval);
+            logInterval = null;
+        }
+    }
+
     previewBtn.addEventListener('click', async function() {
-        const file = sourceFile.files[0];
-        if (!selectedTemplate || !file) {
-            log('请选择模板和来源文件', 'warn');
+        const templateFileInput = templateFile.files[0];
+        const dataFileInput = dataFile.files[0];
+        if (!selectedTemplate || !templateFileInput || !dataFileInput) {
+            log('请选择模板类型,上传模板和数据文件', 'warn');
             return;
         }
-
         this.disabled = true;
         setStatus('正在预览...', 'loading');
-        log(`📖 预览来源数据: ${file.name}`, 'info');
-
+        log(`📖 预览数据: ${dataFileInput.name}`, 'info');
         const formData = new FormData();
-        formData.append('source_file', file);
+        formData.append('data_file', dataFileInput);
         formData.append('template_key', selectedTemplate);
-
         try {
             const response = await fetch('/preview', { method: 'POST', body: formData });
             const result = await response.json();
-
             if (result.error) {
                 log(`❌ 预览失败: ${result.error}`, 'error');
                 setStatus('预览失败', 'error');
                 previewContainer.classList.add('hidden');
                 return;
             }
-
-            sourcePath = result.source_path;
+            dataPath = result.data_path;
             log(`✅ 成功读取 ${result.total_rows} 行数据`, 'success');
             setStatus(`预览成功,共 ${result.total_rows} 行`, 'success');
             rowCount.textContent = `共 ${result.total_rows} 行`;
-
             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) {
             log(`❌ 请求失败: ${err.message}`, 'error');
             setStatus('预览失败', 'error');
@@ -511,130 +626,50 @@
     }
 
     generateBtn.addEventListener('click', async function() {
-        if (!sourcePath) {
+        if (!dataPath) {
             log('请先预览数据', 'warn');
             return;
         }
-
+        const templateFileInput = templateFile.files[0];
+        if (!templateFileInput) {
+            log('请上传模板文件', 'warn');
+            return;
+        }
         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>';
-        }
+        // 始终显示详细日志区域并开始轮询
+        startLogPolling();
 
         const formData = new FormData();
-        formData.append('source_path', sourcePath);
+        formData.append('data_path', dataPath);
         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();
-
+        formData.append('template_file', templateFileInput);
+        formData.append('use_ai', useAI.checked ? 'true' : 'false');
         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;
-            }
-
+            stopLogPolling();
             if (result.error) {
                 log(`❌ 生成失败: ${result.error}`, 'error');
                 setStatus('生成失败', 'error');
-                // 显示错误日志
                 if (result.logs) {
-                    updateAILogDisplay(result.logs);
+                    updateDetailLogDisplay(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} 行`;
+                updateDetailLogDisplay(result.logs);
             }
-
         } catch (err) {
-            // 停止轮询
-            if (logInterval) {
-                clearInterval(logInterval);
-                logInterval = null;
-            }
+            stopLogPolling();
             log(`❌ 请求失败: ${err.message}`, 'error');
             setStatus('生成失败', 'error');
         } finally {
@@ -643,9 +678,8 @@
         }
     });
 
-    // 默认选中 Wayfair
     selectTemplate('wayfair');
-    log('💡 请上传来源Excel文件,然后点击"预览数据"', 'info');
+    log('💡 请选择模板类型,上传模板和数据文件,然后点击"预览数据"', 'info');
 </script>
 </body>
 </html>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 551 - 194
web.py


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor