modified: blueprints/__pycache__/__init__.cpython-39.pyc
modified: blueprints/__pycache__/admin.cpython-39.pyc modified: blueprints/__pycache__/auth.cpython-39.pyc modified: blueprints/__pycache__/brand.cpython-311.pyc modified: blueprints/__pycache__/brand.cpython-39.pyc modified: blueprints/brand.py new file: brand_spider/__pycache__/main.cpython-311.pyc modified: brand_spider/__pycache__/main.cpython-39.pyc new file: brand_spider/__pycache__/web_dec.cpython-311.pyc modified: brand_spider/main.py modified: config.py modified: web_source/admin.html modified: web_source/brand.html modified: web_source/home.html modified: web_source/index.html modified: web_source/login.html new file: web_source/templates_backup/admin.html new file: web_source/templates_backup/brand.html new file: web_source/templates_backup/home.html new file: web_source/templates_backup/index.html new file: web_source/templates_backup/login.html
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>亚马逊 - 南日AI</title>
|
||||
<title>亚马逊 - 数富AI</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
@@ -338,15 +338,15 @@
|
||||
<body>
|
||||
<header class="top-bar">
|
||||
<div class="logo-area">
|
||||
<span class="app-name">南日AI-亚马逊</span>
|
||||
<span class="app-name">数富AI-亚马逊</span>
|
||||
<a href="/home" class="btn-home">返回首页</a>
|
||||
</div>
|
||||
<nav class="nav-tabs">
|
||||
<span class="nav-tab-group">
|
||||
<button type="button" class="nav-tab active" data-panel="brandCheck">品牌检测</button>
|
||||
<button type="button" class="nav-tab" onclick="window.location='/new_web_source/dedupe.html'">总数据去重</button>
|
||||
<button type="button" class="nav-tab" onclick="window.location='/new_web_source/convert.html'">格式转换</button>
|
||||
<button type="button" class="nav-tab" onclick="window.location='/new_web_source/split.html'">表格拆分</button>
|
||||
<button type="button" class="nav-tab" onclick="window.location='/new_web_source/convert.html'">格式转换</button>
|
||||
<!-- 后续增加栏目时在此添加,例如:<button type="button" class="nav-tab" data-panel="other">其他栏目</button> -->
|
||||
</span>
|
||||
<!-- 多组栏目时可用分隔符:<span class="nav-tab-sep" aria-hidden="true"></span> -->
|
||||
@@ -467,6 +467,16 @@
|
||||
var cachedTasks = [];
|
||||
var api = window.pywebview && window.pywebview.api;
|
||||
|
||||
function getUid() {
|
||||
return localStorage.getItem('uid') || '';
|
||||
}
|
||||
|
||||
function withUidHeaders(headers) {
|
||||
headers = headers || {};
|
||||
headers.uid = getUid();
|
||||
return headers;
|
||||
}
|
||||
|
||||
// 顶部栏目切换:点击 nav-tab 时显示对应 data-panel 的 tab-panel
|
||||
document.querySelectorAll('.nav-tab').forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
@@ -572,8 +582,8 @@
|
||||
if (!folder) return;
|
||||
fetch('/api/brand/expand-folder', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
headers: withUidHeaders({ 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ folder: folder })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
@@ -604,14 +614,14 @@
|
||||
|
||||
var pollTaskId = null;
|
||||
var pollTimer = null;
|
||||
var runTaskEventSource = null;
|
||||
var runTaskEventAbortController = null;
|
||||
|
||||
function stopPollAndResetRunUI() {
|
||||
pollTaskId = null;
|
||||
if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
|
||||
if (runTaskEventSource) {
|
||||
runTaskEventSource.close();
|
||||
runTaskEventSource = null;
|
||||
if (runTaskEventAbortController) {
|
||||
try { runTaskEventAbortController.abort(); } catch (e) {}
|
||||
runTaskEventAbortController = null;
|
||||
}
|
||||
var btn = document.getElementById('btnRun');
|
||||
var loadingMsg = document.getElementById('loadingMsg');
|
||||
@@ -645,7 +655,7 @@
|
||||
runProgressWrap.style.display = 'block';
|
||||
btnCancelRun.style.display = 'inline-block';
|
||||
btnCancelRun.onclick = function() {
|
||||
fetch('/api/brand/tasks/' + taskId + '/cancel', { method: 'POST', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
fetch('/api/brand/tasks/' + taskId + '/cancel', { method: 'POST', credentials: 'include', headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' }) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (res.success) onRunTaskFinished('cancelled');
|
||||
@@ -653,28 +663,64 @@
|
||||
});
|
||||
};
|
||||
|
||||
// 通过 SSE 订阅任务完成事件,完成后由后端主动推送,前端收到后立即隐藏进度条和取消按钮
|
||||
// 通过事件流订阅任务完成事件(使用 fetch 以便携带 uid 请求头)
|
||||
var eventsUrl = '/api/brand/tasks/' + taskId + '/events';
|
||||
runTaskEventSource = new EventSource(eventsUrl);
|
||||
runTaskEventSource.onmessage = function(e) {
|
||||
runTaskEventAbortController = new AbortController();
|
||||
(async function() {
|
||||
try {
|
||||
var data = JSON.parse(e.data);
|
||||
if (data && (data.status === 'success' || data.status === 'failed' || data.status === 'cancelled')) {
|
||||
runTaskEventSource.close();
|
||||
runTaskEventSource = null;
|
||||
// tick();
|
||||
onRunTaskFinished(data.status);
|
||||
var resp = await fetch(eventsUrl, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/event-stream' }),
|
||||
signal: runTaskEventAbortController.signal
|
||||
});
|
||||
if (!resp.ok || !resp.body) return;
|
||||
|
||||
var reader = resp.body.getReader();
|
||||
var decoder = new TextDecoder('utf-8');
|
||||
var buffer = '';
|
||||
|
||||
while (true) {
|
||||
var r = await reader.read();
|
||||
if (!r || r.done) break;
|
||||
buffer += decoder.decode(r.value, { stream: true });
|
||||
|
||||
var parts = buffer.split(/\n\n/);
|
||||
buffer = parts.pop();
|
||||
|
||||
var terminalStatus = null;
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i];
|
||||
if (!part) continue;
|
||||
|
||||
var dataLine = part.split('\n').find(function(l) { return l.indexOf('data:') === 0; });
|
||||
if (!dataLine) continue;
|
||||
|
||||
var dataStr = dataLine.slice(5).trim();
|
||||
if (!dataStr) continue;
|
||||
|
||||
try {
|
||||
var data = JSON.parse(dataStr);
|
||||
if (data && (data.status === 'success' || data.status === 'failed' || data.status === 'cancelled')) {
|
||||
terminalStatus = data.status;
|
||||
break;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
if (terminalStatus) {
|
||||
try { runTaskEventAbortController.abort(); } catch (e2) {}
|
||||
runTaskEventAbortController = null;
|
||||
onRunTaskFinished(terminalStatus);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
};
|
||||
runTaskEventSource.onerror = function() {
|
||||
runTaskEventSource.close();
|
||||
runTaskEventSource = null;
|
||||
};
|
||||
})();
|
||||
|
||||
function tick() {
|
||||
if (!pollTaskId) return;
|
||||
fetch('/api/brand/tasks/' + taskId, { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
fetch('/api/brand/tasks/' + taskId, { credentials: 'include', headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' }) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (!res.success || !res.task) return;
|
||||
@@ -701,8 +747,8 @@
|
||||
var st2 = (t.status || '').toLowerCase();
|
||||
if (st2 === 'running') {
|
||||
fetch('/api/brand/tasks/' + taskId + '/line-progress', {
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
credentials: 'include',
|
||||
headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res2) {
|
||||
@@ -730,7 +776,7 @@
|
||||
});
|
||||
}
|
||||
tick();
|
||||
pollTimer = setInterval(tick, 5000);
|
||||
pollTimer = setInterval(tick, 10000);
|
||||
}
|
||||
|
||||
document.getElementById('btnRun').onclick = function() {
|
||||
@@ -748,8 +794,8 @@
|
||||
loadingMsg.style.display = 'inline';
|
||||
fetch('/api/brand/run', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
headers: withUidHeaders({ 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ paths: selectedPaths, strategy: strategy, task_type: 1 })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
@@ -769,8 +815,8 @@
|
||||
} else {
|
||||
fetch('/api/brand/tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
headers: withUidHeaders({ 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ paths: selectedPaths, strategy: strategy, task_type: 2 })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
@@ -787,7 +833,7 @@
|
||||
};
|
||||
|
||||
function loadTasks() {
|
||||
fetch('/api/brand/tasks', { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
fetch('/api/brand/tasks', { credentials: 'include', headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' }) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
var list = document.getElementById('taskList');
|
||||
@@ -854,7 +900,7 @@
|
||||
if (target.classList && (target.classList.contains('js-cancel-task') || target.closest('.js-cancel-task'))) {
|
||||
var btn = target.classList.contains('js-cancel-task') ? target : target.closest('.js-cancel-task');
|
||||
if (!btn) return;
|
||||
fetch('/api/brand/tasks/' + taskId + '/cancel', { method: 'POST', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
fetch('/api/brand/tasks/' + taskId + '/cancel', { method: 'POST', credentials: 'include', headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' }) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (res.success) { showToast('已取消'); loadTasks(); } else { showToast(res.error || '取消失败'); }
|
||||
@@ -864,7 +910,7 @@
|
||||
if (target.classList && (target.classList.contains('js-delete-task') || target.closest('.js-delete-task'))) {
|
||||
var btn = target.classList.contains('js-delete-task') ? target : target.closest('.js-delete-task');
|
||||
if (!btn) return;
|
||||
fetch('/api/brand/tasks/' + taskId, { method: 'DELETE', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
fetch('/api/brand/tasks/' + taskId, { method: 'DELETE', credentials: 'include', headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' }) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (res.success) { showToast('已删除'); loadTasks(); } else { showToast(res.error || '删除失败'); }
|
||||
@@ -902,7 +948,40 @@
|
||||
a.style.pointerEvents = '';
|
||||
}
|
||||
} else {
|
||||
window.open('/api/brand/download/' + taskId, '_blank');
|
||||
fetch('/api/brand/download/' + taskId, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: withUidHeaders({ 'X-Requested-With': 'XMLHttpRequest' }),
|
||||
redirect: 'manual'
|
||||
})
|
||||
.then(function(res) {
|
||||
if (res.status === 301 || res.status === 302) {
|
||||
var loc = res.headers.get('Location');
|
||||
if (loc) {
|
||||
window.open(loc, '_blank');
|
||||
return;
|
||||
}
|
||||
}
|
||||
var ct = (res.headers && res.headers.get && res.headers.get('content-type')) ? res.headers.get('content-type') : '';
|
||||
if (ct && ct.indexOf('application/json') >= 0) {
|
||||
return res.json().then(function(errObj) {
|
||||
throw new Error((errObj && (errObj.error || errObj.msg)) || '下载失败');
|
||||
});
|
||||
}
|
||||
return res.blob().then(function(blob) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a2 = document.createElement('a');
|
||||
a2.href = url;
|
||||
a2.download = 'brand_task_' + taskId + '.zip';
|
||||
document.body.appendChild(a2);
|
||||
a2.click();
|
||||
a2.remove();
|
||||
setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast('下载失败:' + (err && err.message ? err.message : err));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user