index.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Excel 自动填表工具</title>
  7. <style>
  8. * { margin: 0; padding: 0; box-sizing: border-box; }
  9. body {
  10. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  11. background: #f5f7fa;
  12. color: #333;
  13. padding: 20px;
  14. }
  15. .container {
  16. max-width: 1100px;
  17. margin: 0 auto;
  18. background: #fff;
  19. border-radius: 12px;
  20. box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  21. padding: 30px;
  22. }
  23. h1 {
  24. font-size: 24px;
  25. color: #1a1a2e;
  26. margin-bottom: 8px;
  27. display: flex;
  28. align-items: center;
  29. gap: 10px;
  30. }
  31. .subtitle {
  32. color: #888;
  33. font-size: 14px;
  34. margin-bottom: 24px;
  35. border-bottom: 1px solid #eee;
  36. padding-bottom: 16px;
  37. display: flex;
  38. justify-content: space-between;
  39. align-items: center;
  40. flex-wrap: wrap;
  41. gap: 10px;
  42. }
  43. .form-group {
  44. margin-bottom: 18px;
  45. }
  46. .form-group label {
  47. display: block;
  48. font-weight: 600;
  49. font-size: 14px;
  50. margin-bottom: 5px;
  51. color: #444;
  52. }
  53. .form-group .hint {
  54. font-size: 12px;
  55. color: #999;
  56. font-weight: 400;
  57. }
  58. .form-row {
  59. display: flex;
  60. gap: 20px;
  61. flex-wrap: wrap;
  62. }
  63. .form-row .form-group {
  64. flex: 1;
  65. min-width: 200px;
  66. }
  67. select, input[type="file"] {
  68. width: 100%;
  69. padding: 10px 12px;
  70. border: 1px solid #d1d5db;
  71. border-radius: 8px;
  72. font-size: 14px;
  73. background: #fafbfc;
  74. transition: border-color 0.2s;
  75. }
  76. select:focus, input:focus {
  77. border-color: #4f8cf7;
  78. outline: none;
  79. box-shadow: 0 0 0 3px rgba(79, 140, 247, 0.15);
  80. }
  81. .template-options {
  82. display: flex;
  83. gap: 12px;
  84. flex-wrap: wrap;
  85. }
  86. .template-option {
  87. flex: 1;
  88. min-width: 120px;
  89. padding: 14px 20px;
  90. border: 2px solid #e5e7eb;
  91. border-radius: 10px;
  92. cursor: pointer;
  93. text-align: center;
  94. font-weight: 600;
  95. font-size: 15px;
  96. transition: all 0.2s;
  97. background: #fafbfc;
  98. }
  99. .template-option:hover {
  100. border-color: #4f8cf7;
  101. background: #f0f4ff;
  102. }
  103. .template-option.active {
  104. border-color: #4f8cf7;
  105. background: #e8effe;
  106. box-shadow: 0 0 0 3px rgba(79, 140, 247, 0.2);
  107. }
  108. .template-option .badge {
  109. display: block;
  110. font-size: 11px;
  111. font-weight: 400;
  112. color: #888;
  113. margin-top: 4px;
  114. }
  115. .file-upload-wrapper {
  116. display: flex;
  117. align-items: center;
  118. gap: 12px;
  119. }
  120. .file-upload-wrapper input[type="file"] {
  121. padding: 8px;
  122. flex: 1;
  123. }
  124. .file-name {
  125. font-size: 13px;
  126. color: #666;
  127. white-space: nowrap;
  128. overflow: hidden;
  129. text-overflow: ellipsis;
  130. max-width: 200px;
  131. }
  132. .btn {
  133. padding: 10px 28px;
  134. border: none;
  135. border-radius: 8px;
  136. font-size: 15px;
  137. font-weight: 600;
  138. cursor: pointer;
  139. transition: all 0.2s;
  140. display: inline-flex;
  141. align-items: center;
  142. gap: 8px;
  143. }
  144. .btn-primary { background: #4f8cf7; color: #fff; }
  145. .btn-primary:hover { background: #3a7ae8; transform: translateY(-1px); }
  146. .btn-primary:disabled { background: #b0c8f5; cursor: not-allowed; transform: none; }
  147. .btn-success { background: #34c759; color: #fff; }
  148. .btn-success:hover { background: #2db84e; }
  149. .btn-success:disabled { background: #9de0b2; cursor: not-allowed; }
  150. .btn-secondary { background: #e8ecf1; color: #444; }
  151. .btn-secondary:hover { background: #d5dbe3; }
  152. .btn-danger { background: #ef4444; color: #fff; }
  153. .btn-danger:hover { background: #dc2626; }
  154. .actions {
  155. display: flex;
  156. gap: 12px;
  157. flex-wrap: wrap;
  158. margin: 20px 0 16px;
  159. padding: 16px 0;
  160. border-top: 1px solid #eee;
  161. border-bottom: 1px solid #eee;
  162. }
  163. .log-area {
  164. background: #1a1a2e;
  165. color: #e4e4e4;
  166. border-radius: 8px;
  167. padding: 16px 20px;
  168. font-family: 'Courier New', monospace;
  169. font-size: 12px;
  170. max-height: 320px;
  171. overflow-y: auto;
  172. white-space: pre-wrap;
  173. word-break: break-all;
  174. line-height: 1.6;
  175. margin-top: 16px;
  176. }
  177. .log-area .log-success { color: #4ade80; }
  178. .log-area .log-error { color: #f87171; }
  179. .log-area .log-warn { color: #fbbf24; }
  180. .log-area .log-info { color: #60a5fa; }
  181. .preview-table-wrapper {
  182. overflow-x: auto;
  183. margin-top: 16px;
  184. border-radius: 8px;
  185. border: 1px solid #e5e7eb;
  186. }
  187. .preview-table {
  188. width: 100%;
  189. border-collapse: collapse;
  190. font-size: 13px;
  191. min-width: 600px;
  192. }
  193. .preview-table th {
  194. background: #f3f4f6;
  195. padding: 10px 12px;
  196. text-align: left;
  197. font-weight: 600;
  198. border-bottom: 2px solid #e5e7eb;
  199. white-space: nowrap;
  200. position: sticky;
  201. top: 0;
  202. z-index: 1;
  203. }
  204. .preview-table td {
  205. padding: 8px 12px;
  206. border-bottom: 1px solid #e5e7eb;
  207. max-width: 200px;
  208. overflow: hidden;
  209. text-overflow: ellipsis;
  210. white-space: nowrap;
  211. }
  212. .preview-table tr:hover td { background: #f9fafb; }
  213. .status-badge {
  214. display: inline-block;
  215. padding: 4px 14px;
  216. border-radius: 20px;
  217. font-size: 13px;
  218. font-weight: 500;
  219. }
  220. .status-badge.idle { background: #e5e7eb; color: #6b7280; }
  221. .status-badge.loading { background: #fef3c7; color: #d97706; }
  222. .status-badge.success { background: #d1fae5; color: #065f46; }
  223. .status-badge.error { background: #fee2e2; color: #991b1b; }
  224. .flex { display: flex; align-items: center; gap: 12px; }
  225. .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
  226. .mt-8 { margin-top: 8px; }
  227. .mt-12 { margin-top: 12px; }
  228. .text-sm { font-size: 13px; }
  229. .text-muted { color: #888; }
  230. .hidden { display: none !important; }
  231. .download-section {
  232. background: #ecfdf5;
  233. border: 1px solid #6ee7b7;
  234. border-radius: 8px;
  235. padding: 16px 20px;
  236. display: none;
  237. align-items: center;
  238. justify-content: space-between;
  239. flex-wrap: wrap;
  240. gap: 12px;
  241. margin-top: 12px;
  242. }
  243. .download-section.show { display: flex; }
  244. .config-info {
  245. font-size: 13px;
  246. color: #666;
  247. background: #f3f4f6;
  248. padding: 6px 14px;
  249. border-radius: 6px;
  250. display: inline-block;
  251. }
  252. .config-info span { font-weight: 600; }
  253. #aiLogContainer {
  254. margin-top: 12px;
  255. }
  256. #aiLogArea {
  257. background: #1a1a2e;
  258. color: #e4e4e4;
  259. border-radius: 8px;
  260. padding: 12px 16px;
  261. font-family: 'Courier New', monospace;
  262. font-size: 12px;
  263. max-height: 200px;
  264. overflow-y: auto;
  265. white-space: pre-wrap;
  266. word-break: break-all;
  267. line-height: 1.6;
  268. }
  269. #aiLogArea .log-success { color: #4ade80; }
  270. #aiLogArea .log-error { color: #f87171; }
  271. #aiLogArea .log-warn { color: #fbbf24; }
  272. #aiLogArea .log-info { color: #60a5fa; }
  273. </style>
  274. </head>
  275. <body>
  276. <div class="container">
  277. <h1>📊 Excel 自动填表工具</h1>
  278. <div class="subtitle">
  279. <span>选择模板 → 上传来源数据 → 预览 → 生成并下载</span>
  280. <span class="status-badge idle" id="statusBadge">就绪</span>
  281. </div>
  282. <!-- 模板选择 -->
  283. <div class="form-group">
  284. <label>📁 选择平台模板</label>
  285. <div class="template-options" id="templateOptions">
  286. <div class="template-option active" data-key="wayfair" onclick="selectTemplate('wayfair')">
  287. 🏢 Wayfair
  288. <span class="badge">wayfair模板.xlsx</span>
  289. </div>
  290. <div class="template-option" data-key="amazon" onclick="selectTemplate('amazon')">
  291. 🛒 亚马逊
  292. <span class="badge">亚马逊模板.xlsx</span>
  293. </div>
  294. </div>
  295. <div class="mt-8" id="configInfo">
  296. <span class="config-info">✅ 已选择 <span>Wayfair</span> 模板 | 来源表头: 第 2 行 | 模板表头: 第 4 行</span>
  297. </div>
  298. </div>
  299. <!-- 文件上传 -->
  300. <div class="form-group">
  301. <label>📄 来源数据 Excel</label>
  302. <div class="file-upload-wrapper">
  303. <input type="file" id="sourceFile" accept=".xlsx,.xls">
  304. <span class="file-name" id="fileName">未选择文件</span>
  305. </div>
  306. </div>
  307. <div class="form-group" style="margin-top: 12px;">
  308. <label style="display:flex;align-items:center;gap:10px;cursor:pointer;">
  309. <input type="checkbox" id="useAI" checked>
  310. 🤖 启用 AI 自动填写(识别产品主图,自动填充属性)
  311. <span class="hint">(需要产品主图URL)</span>
  312. </label>
  313. </div>
  314. <!-- 操作按钮 -->
  315. <div class="actions">
  316. <button class="btn btn-primary" id="previewBtn" disabled>🔍 预览数据</button>
  317. <button class="btn btn-success" id="generateBtn" disabled>🚀 生成并下载</button>
  318. <button class="btn btn-secondary" id="clearLogBtn">🗑️ 清空日志</button>
  319. </div>
  320. <div class="flex-between">
  321. <span id="statusText" class="text-muted text-sm">请选择模板和来源文件</span>
  322. <span id="rowCount" class="text-muted text-sm"></span>
  323. </div>
  324. <!-- 预览 -->
  325. <div id="previewContainer" class="hidden">
  326. <div class="flex-between mt-8">
  327. <span class="text-sm" style="font-weight:600;">📋 数据预览 (前10行)</span>
  328. </div>
  329. <div class="preview-table-wrapper">
  330. <table class="preview-table" id="previewTable">
  331. <thead id="previewHead"></thead>
  332. <tbody id="previewBody"></tbody>
  333. </table>
  334. </div>
  335. </div>
  336. <!-- 下载 -->
  337. <div class="download-section" id="downloadSection">
  338. <span style="font-weight:600;color:#065f46;">✅ 生成完成!</span>
  339. <span id="downloadInfo"></span>
  340. <a href="#" id="downloadLink" class="btn btn-success" style="text-decoration:none;">📥 下载文件</a>
  341. </div>
  342. <div id="aiLogContainer" class="hidden">
  343. <div class="flex-between mt-12">
  344. <span style="font-weight:600;font-size:14px;">🤖 AI 处理日志</span>
  345. <span id="aiProgress" class="text-muted text-sm"></span>
  346. </div>
  347. <div class="log-area" id="aiLogArea" style="max-height:200px;font-size:11px;margin-top:8px;">
  348. <span class="log-info">💡 AI 日志将在这里显示...</span>
  349. </div>
  350. </div>
  351. <!-- 日志 -->
  352. <div class="log-area" id="logArea">
  353. <span class="log-info">💡 系统就绪,请选择模板并上传来源文件</span>
  354. </div>
  355. </div>
  356. <script>
  357. const templateOptions = document.querySelectorAll('.template-option');
  358. const sourceFile = document.getElementById('sourceFile');
  359. const fileName = document.getElementById('fileName');
  360. const previewBtn = document.getElementById('previewBtn');
  361. const generateBtn = document.getElementById('generateBtn');
  362. const clearLogBtn = document.getElementById('clearLogBtn');
  363. const logArea = document.getElementById('logArea');
  364. const statusText = document.getElementById('statusText');
  365. const statusBadge = document.getElementById('statusBadge');
  366. const rowCount = document.getElementById('rowCount');
  367. const previewContainer = document.getElementById('previewContainer');
  368. const previewHead = document.getElementById('previewHead');
  369. const previewBody = document.getElementById('previewBody');
  370. const downloadSection = document.getElementById('downloadSection');
  371. const downloadLink = document.getElementById('downloadLink');
  372. const downloadInfo = document.getElementById('downloadInfo');
  373. const configInfo = document.getElementById('configInfo');
  374. let selectedTemplate = 'wayfair';
  375. let sourcePath = null;
  376. const templateNames = { wayfair: 'Wayfair', amazon: '亚马逊' };
  377. function selectTemplate(key) {
  378. selectedTemplate = key;
  379. templateOptions.forEach(el => {
  380. el.classList.toggle('active', el.dataset.key === key);
  381. });
  382. fetch('/get_config')
  383. .then(res => res.json())
  384. .then(data => {
  385. const config = data.config[key];
  386. if (config) {
  387. configInfo.innerHTML = `
  388. <span class="config-info">
  389. ✅ 已选择 <span>${templateNames[key]}</span> 模板
  390. | 来源表头: 第 ${config.source_header} 行
  391. | 模板表头: 第 ${config.template_header} 行
  392. | 文件: ${config.file}
  393. </span>
  394. `;
  395. }
  396. });
  397. checkReady();
  398. log(`📁 选择模板: ${templateNames[key] || key}`, 'info');
  399. }
  400. sourceFile.addEventListener('change', function() {
  401. if (this.files.length > 0) {
  402. fileName.textContent = this.files[0].name;
  403. log(`📄 选择文件: ${this.files[0].name}`, 'info');
  404. } else {
  405. fileName.textContent = '未选择文件';
  406. }
  407. checkReady();
  408. });
  409. function checkReady() {
  410. const hasTemplate = selectedTemplate !== null;
  411. const hasFile = sourceFile.files.length > 0;
  412. previewBtn.disabled = !(hasTemplate && hasFile);
  413. generateBtn.disabled = true;
  414. if (hasTemplate && hasFile) {
  415. setStatus('已选择模板和来源文件,点击预览', 'idle');
  416. } else if (!hasTemplate) {
  417. setStatus('请选择模板', 'idle');
  418. } else {
  419. setStatus('请选择来源文件', 'idle');
  420. }
  421. }
  422. function log(msg, type = 'info') {
  423. const colors = { info: 'log-info', success: 'log-success', error: 'log-error', warn: 'log-warn' };
  424. const prefix = { info: 'ℹ️', success: '✅', error: '❌', warn: '⚠️' };
  425. const timestamp = new Date().toLocaleTimeString();
  426. const line = document.createElement('div');
  427. line.innerHTML = `<span style="color:#888;">[${timestamp}]</span> <span class="${colors[type] || 'log-info'}">${prefix[type] || 'ℹ️'} ${msg}</span>`;
  428. logArea.appendChild(line);
  429. logArea.scrollTop = logArea.scrollHeight;
  430. }
  431. function setStatus(text, type = 'idle') {
  432. statusText.textContent = text;
  433. const labels = { idle: '就绪', loading: '处理中...', success: '成功', error: '失败' };
  434. statusBadge.textContent = labels[type] || '就绪';
  435. statusBadge.className = `status-badge ${type}`;
  436. }
  437. function clearLog() {
  438. logArea.innerHTML = '';
  439. log('日志已清空', 'info');
  440. }
  441. clearLogBtn.addEventListener('click', clearLog);
  442. previewBtn.addEventListener('click', async function() {
  443. const file = sourceFile.files[0];
  444. if (!selectedTemplate || !file) {
  445. log('请选择模板和来源文件', 'warn');
  446. return;
  447. }
  448. this.disabled = true;
  449. setStatus('正在预览...', 'loading');
  450. log(`📖 预览来源数据: ${file.name}`, 'info');
  451. const formData = new FormData();
  452. formData.append('source_file', file);
  453. formData.append('template_key', selectedTemplate);
  454. try {
  455. const response = await fetch('/preview', { method: 'POST', body: formData });
  456. const result = await response.json();
  457. if (result.error) {
  458. log(`❌ 预览失败: ${result.error}`, 'error');
  459. setStatus('预览失败', 'error');
  460. previewContainer.classList.add('hidden');
  461. return;
  462. }
  463. sourcePath = result.source_path;
  464. log(`✅ 成功读取 ${result.total_rows} 行数据`, 'success');
  465. setStatus(`预览成功,共 ${result.total_rows} 行`, 'success');
  466. rowCount.textContent = `共 ${result.total_rows} 行`;
  467. previewHead.innerHTML = `<tr>${result.columns.map(c => `<th>${escapeHtml(c)}</th>`).join('')}</tr>`;
  468. previewBody.innerHTML = result.data.map(row => {
  469. return `<tr>${result.columns.map(c => `<td title="${escapeHtml(String(row[c] || ''))}">${escapeHtml(String(row[c] || ''))}</td>`).join('')}</tr>`;
  470. }).join('');
  471. previewContainer.classList.remove('hidden');
  472. generateBtn.disabled = false;
  473. } catch (err) {
  474. log(`❌ 请求失败: ${err.message}`, 'error');
  475. setStatus('预览失败', 'error');
  476. } finally {
  477. this.disabled = false;
  478. }
  479. });
  480. function escapeHtml(text) {
  481. const div = document.createElement('div');
  482. div.textContent = text;
  483. return div.innerHTML;
  484. }
  485. generateBtn.addEventListener('click', async function() {
  486. if (!sourcePath) {
  487. log('请先预览数据', 'warn');
  488. return;
  489. }
  490. this.disabled = true;
  491. previewBtn.disabled = true;
  492. setStatus('正在生成...', 'loading');
  493. log('🚀 开始生成并追加到模板...', 'info');
  494. // 显示AI日志区域
  495. const aiLogContainer = document.getElementById('aiLogContainer');
  496. if (aiLogContainer) {
  497. aiLogContainer.classList.remove('hidden');
  498. document.getElementById('aiLogArea').innerHTML = '<span class="log-info">⏳ AI处理中,请等待...</span>';
  499. }
  500. const formData = new FormData();
  501. formData.append('source_path', sourcePath);
  502. formData.append('template_key', selectedTemplate);
  503. const useAICheckbox = document.getElementById('useAI');
  504. formData.append('use_ai', useAICheckbox ? useAICheckbox.checked : false);
  505. // 开始轮询AI日志
  506. let logInterval = null;
  507. let pollCount = 0;
  508. const maxPolls = 300; // 最多轮询300次(10分钟)
  509. function startAILogPolling() {
  510. if (logInterval) clearInterval(logInterval);
  511. logInterval = setInterval(async function() {
  512. pollCount++;
  513. try {
  514. const response = await fetch('/get_ai_logs');
  515. const data = await response.json();
  516. if (data.logs && data.logs.length > 0) {
  517. updateAILogDisplay(data.logs);
  518. }
  519. // 如果轮询超过最大次数,自动停止
  520. if (pollCount >= maxPolls) {
  521. clearInterval(logInterval);
  522. logInterval = null;
  523. }
  524. } catch (e) {
  525. // 静默失败,不中断轮询
  526. }
  527. }, 2000); // 每2秒刷新一次
  528. }
  529. function updateAILogDisplay(logs) {
  530. const aiLogArea = document.getElementById('aiLogArea');
  531. if (!aiLogArea) return;
  532. // 只显示最近的日志(避免太多)
  533. const recentLogs = logs.slice(-50);
  534. aiLogArea.innerHTML = recentLogs.map(log => {
  535. const colors = {
  536. info: 'log-info',
  537. success: 'log-success',
  538. error: 'log-error',
  539. warn: 'log-warn'
  540. };
  541. const prefix = {
  542. info: 'ℹ️',
  543. success: '✅',
  544. error: '❌',
  545. warn: '⚠️'
  546. };
  547. const level = log.level || 'info';
  548. return `<div><span style="color:#888;">[${log.time}]</span> <span class="${colors[level] || 'log-info'}">${prefix[level] || 'ℹ️'} ${log.message}</span></div>`;
  549. }).join('');
  550. aiLogArea.scrollTop = aiLogArea.scrollHeight;
  551. }
  552. // 开始轮询
  553. startAILogPolling();
  554. try {
  555. const response = await fetch('/generate', {
  556. method: 'POST',
  557. body: formData
  558. });
  559. const result = await response.json();
  560. // 停止轮询
  561. if (logInterval) {
  562. clearInterval(logInterval);
  563. logInterval = null;
  564. }
  565. if (result.error) {
  566. log(`❌ 生成失败: ${result.error}`, 'error');
  567. setStatus('生成失败', 'error');
  568. // 显示错误日志
  569. if (result.logs) {
  570. updateAILogDisplay(result.logs);
  571. }
  572. return;
  573. }
  574. log(`✅ ${result.message}`, 'success');
  575. setStatus(`生成成功,追加 ${result.filled_count} 行`, 'success');
  576. downloadSection.classList.add('show');
  577. downloadLink.href = result.download_url;
  578. downloadInfo.textContent = `已追加 ${result.filled_count} 行数据`;
  579. // 显示最终AI日志
  580. if (result.logs) {
  581. updateAILogDisplay(result.logs);
  582. }
  583. // 如果AI处理了数据,显示成功信息
  584. if (result.ai_used && result.ai_rows > 0) {
  585. document.getElementById('aiProgress').textContent = `✅ AI识别完成,成功 ${result.ai_rows} 行`;
  586. }
  587. } catch (err) {
  588. // 停止轮询
  589. if (logInterval) {
  590. clearInterval(logInterval);
  591. logInterval = null;
  592. }
  593. log(`❌ 请求失败: ${err.message}`, 'error');
  594. setStatus('生成失败', 'error');
  595. } finally {
  596. this.disabled = false;
  597. previewBtn.disabled = false;
  598. }
  599. });
  600. // 默认选中 Wayfair
  601. selectTemplate('wayfair');
  602. log('💡 请上传来源Excel文件,然后点击"预览数据"', 'info');
  603. </script>
  604. </body>
  605. </html>