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:
铭坤
2026-03-26 16:39:39 +08:00
parent 506eb0faef
commit a5da06537e
21 changed files with 8897 additions and 1266 deletions

View File

@@ -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));
});
}
});