|
|
@@ -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>
|