更新后端新增内容

This commit is contained in:
super
2026-03-26 16:42:25 +08:00
parent 21c6f41c69
commit 32a9494dd6
63 changed files with 1943 additions and 415 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>格式转换 - 数富AI</title>
<script type="module" crossorigin src="/assets/convert.js"></script>
<link rel="modulepreload" crossorigin href="/assets/pywebview-BRrok0pS.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-IqgMfeBe.css">
<link rel="modulepreload" crossorigin href="/assets/pywebview-Bl6M97uH.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-8G_9KMXd.css">
<link rel="stylesheet" crossorigin href="/assets/convert-Cfr3mUjs.css">
</head>
<body>

View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数据去重 - 数富AI</title>
<script type="module" crossorigin src="/assets/dedupe.js"></script>
<link rel="modulepreload" crossorigin href="/assets/pywebview-BRrok0pS.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-IqgMfeBe.css">
<link rel="modulepreload" crossorigin href="/assets/pywebview-Bl6M97uH.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-8G_9KMXd.css">
<link rel="stylesheet" crossorigin href="/assets/dedupe-CS18ls2z.css">
</head>
<body>

View File

@@ -0,0 +1,15 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>删除品牌 - 数富AI</title>
<script type="module" crossorigin src="/assets/delete-brand.js"></script>
<link rel="modulepreload" crossorigin href="/assets/pywebview-Bl6M97uH.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-8G_9KMXd.css">
<link rel="stylesheet" crossorigin href="/assets/delete-brand-D1WtGorm.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数据拆分 - 数富AI</title>
<script type="module" crossorigin src="/assets/split.js"></script>
<link rel="modulepreload" crossorigin href="/assets/pywebview-BRrok0pS.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-IqgMfeBe.css">
<link rel="modulepreload" crossorigin href="/assets/pywebview-Bl6M97uH.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-8G_9KMXd.css">
<link rel="stylesheet" crossorigin href="/assets/split-mwLUJT1K.css">
</head>
<body>

View File

@@ -1,9 +1,9 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="zh-CN">
<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 {
@@ -38,7 +38,7 @@
}
.btn-home:hover { color: #fff; background: #2a2a2a; }
/* 顶部栏目导航(容器化,便于后续增加栏目) */
/* 椤堕儴鏍忕洰瀵艰埅锛堝鍣ㄥ寲锛屼究浜庡悗缁鍔犳爮鐩級 */
.nav-tabs {
display: flex;
align-items: center;
@@ -87,7 +87,7 @@
display: flex;
height: calc(100vh - 56px);
}
/* 栏目内容容器:每个栏目一个 .tab-panel,通过 data-panel 与导航对应 */
/* 鏍忕洰鍐呭瀹瑰櫒锛氭瘡涓爮鐩竴涓?.tab-panel锛岄€氳繃 data-panel 涓庡鑸搴?*/
.tab-panel {
display: none;
flex: 1;
@@ -338,87 +338,87 @@
<body>
<header class="top-bar">
<div class="logo-area">
<span class="app-name">南日AI-亚马逊</span>
<a href="/home" class="btn-home">返回首页</a>
<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" data-panel="other">其他栏目</button> -->
<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" data-panel="other">鍏朵粬鏍忕洰</button> -->
</span>
<!-- 多组栏目时可用分隔符:<span class="nav-tab-sep" aria-hidden="true"></span> -->
<!-- 澶氱粍鏍忕洰鏃跺彲鐢ㄥ垎闅旂锛?span class="nav-tab-sep" aria-hidden="true"></span> -->
</nav>
<div class="top-right"></div>
</header>
<div class="main-content">
<!-- 品牌检测栏目容器;后续增加栏目时复制此结构,改 data-panel 与 id并去掉 .active -->
<!-- 鍝佺墝妫€娴嬫爮鐩鍣紱鍚庣画澧炲姞鏍忕洰鏃跺鍒舵缁撴瀯锛屾敼 data-panel 涓?id锛屽苟鍘绘帀 .active -->
<div class="tab-panel active" data-panel="brandCheck" id="panel-brandCheck">
<aside class="left-panel">
<div class="section-title">选择文件</div>
<div class="section-title">閫夋嫨鏂囦欢</div>
<div class="upload-zone" id="uploadZone">
<div class="hint">选择 .xlsx 文件或文件夹(将读取该文件夹下所有 xlsx</div>
<div class="hint">閫夋嫨 .xlsx 鏂囦欢鎴栨枃浠跺す锛堝皢璇诲彇璇ユ枃浠跺す涓嬫墍鏈?xlsx锛?/div>
<div class="btns">
<button type="button" class="opt-btn" id="btnSelectFiles">选择 Excel 文件</button>
<button type="button" class="opt-btn" id="btnSelectFolder">选择文件夹</button>
<button type="button" class="opt-btn" id="btnSelectFiles">閫夋嫨 Excel 鏂囦欢</button>
<button type="button" class="opt-btn" id="btnSelectFolder">閫夋嫨鏂囦欢澶?/button>
</div>
<div class="selected-files" id="selectedFiles"></div>
</div>
<div class="section-title">运行方式</div>
<div class="section-title">杩愯鏂瑰紡</div>
<div class="option-group">
<label class="radio-item">
<input type="radio" name="runMode" value="immediate" checked>
<div>
<span class="label">立即运行</span>
<div class="desc">立刻执行,执行完成后可下载结果</div>
<span class="label">绔嬪嵆杩愯</span>
<div class="desc">绔嬪埢鎵ц锛屾墽琛屽畬鎴愬悗鍙笅杞界粨鏋?/div>
</div>
</label>
<label class="radio-item">
<input type="radio" name="runMode" value="task">
<div>
<span class="label">添加任务</span>
<div class="desc">数量较多时建议添加任务,后台执行,可在右侧任务队列查看状态与下载结果</div>
<span class="label">娣诲姞浠诲姟</span>
<div class="desc">鏁伴噺杈冨鏃跺缓璁坊鍔犱换鍔★紝鍚庡彴鎵ц锛屽彲鍦ㄥ彸渚т换鍔¢槦鍒楁煡鐪嬬姸鎬佷笌涓嬭浇缁撴灉</div>
</div>
</label>
</div>
<div class="section-title">匹配方式</div>
<div class="section-title">鍖归厤鏂瑰紡</div>
<div class="option-group">
<label class="radio-item">
<input type="radio" name="matchStrategy" value="Terms" checked>
<div>
<span class="label">精确匹配表达式</span>
<div class="desc">使用精确匹配,适合严格匹配品牌名称</div>
<span class="label">绮剧‘鍖归厤琛ㄨ揪寮?/span>
<div class="desc">浣跨敤绮剧‘鍖归厤锛岄€傚悎涓ユ牸鍖归厤鍝佺墝鍚嶇О</div>
</div>
</label>
<label class="radio-item">
<input type="radio" name="matchStrategy" value="Simple">
<div>
<span class="label">嵌入(结果包含输入的词语)</span>
<div class="desc">使用嵌入策略,结果中包含输入的品牌词语</div>
<span class="label">宓屽叆锛堢粨鏋滃寘鍚緭鍏ョ殑璇嶈锛?/span>
<div class="desc">浣跨敤宓屽叆绛栫暐锛岀粨鏋滀腑鍖呭惈杈撳叆鐨勫搧鐗岃瘝璇?/div>
</div>
</label>
</div>
<div class="run-row" id="runRow">
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:10px;">
<button type="button" class="btn-run" id="btnRun">立即运行</button>
<span class="loading-msg" id="loadingMsg" style="display:none;">生成中,请稍候…</span>
<button type="button" class="btn-run" id="btnRun">绔嬪嵆杩愯</button>
<span class="loading-msg" id="loadingMsg" style="display:none;">鐢熸垚涓紝璇风◢鍊欌€?/span>
<div class="progress-wrap" id="runProgressWrap" style="display:none;">
<div class="progress-bar-bg"><div class="progress-bar-fill" id="runProgressFill" style="width:0%;"></div></div>
<span class="hint" style="font-size:12px;color:#888;" id="runProgressText">0 / 0</span>
</div>
<!-- 当前文件行级进度(不经数据库中转,只读内存接口) -->
<!-- 褰撳墠鏂囦欢琛岀骇杩涘害锛堜笉缁忔暟鎹簱涓浆锛屽彧璇诲唴瀛樻帴鍙o級 -->
<div class="progress-wrap" id="runLineProgressWrap" style="display:none;">
<div class="progress-bar-bg"><div class="progress-bar-fill" id="runLineProgressFill" style="width:0%;"></div></div>
<span class="hint" style="font-size:12px;color:#888;" id="runLineProgressText">0 / 0</span>
</div>
<button type="button" class="btn-cancel-run" id="btnCancelRun" style="display:none;">取消生成</button>
<button type="button" class="btn-cancel-run" id="btnCancelRun" style="display:none;">鍙栨秷鐢熸垚</button>
</div>
</div>
<div class="template-download-row" id="templateDownloadRow" role="button" tabindex="0" title="下载品牌文档格式模板(选择保存位置)">
<div class="template-download-row" id="templateDownloadRow" role="button" tabindex="0" title="涓嬭浇鍝佺墝鏂囨。鏍煎紡妯℃澘锛堥€夋嫨淇濆瓨浣嶇疆锛?>
<span class="template-download-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z" stroke="rgba(255,255,255,0.9)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
@@ -427,11 +427,11 @@
</svg>
</span>
<span class="template-download-text">
<span class="title">模板下载</span>
<span class="hint">品牌文档格式_模板.xlsx · 点击选择保存位置</span>
<span class="title">妯℃澘涓嬭浇</span>
<span class="hint">鍝佺墝鏂囨。鏍煎紡_妯℃澘.xlsx 路 鐐瑰嚮閫夋嫨淇濆瓨浣嶇疆</span>
</span>
</div>
<div class="template-download-row" id="templateDownloadRow2" role="button" tabindex="0" title="下载模板2以文件夹方式上传">
<div class="template-download-row" id="templateDownloadRow2" role="button" tabindex="0" title="涓嬭浇妯℃澘2锛堜互鏂囦欢澶规柟寮忎笂浼狅級">
<span class="template-download-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z" stroke="rgba(255,255,255,0.9)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
@@ -440,20 +440,20 @@
</svg>
</span>
<span class="template-download-text">
<span class="title">模板2下载</span>
<span class="hint">以文件夹方式上传.zip · 点击下载</span>
<span class="title">妯℃澘2涓嬭浇</span>
<span class="hint">浠ユ枃浠跺す鏂瑰紡涓婁紶.zip 路 鐐瑰嚮涓嬭浇</span>
</span>
</div>
</aside>
<div class="right-panel">
<div class="panel-header">
<span class="panel-header-title">任务队列</span>
<button type="button" class="btn-refresh-tasks" id="btnRefreshTasks">刷新任务状态</button>
<span class="panel-header-title">浠诲姟闃熷垪</span>
<button type="button" class="btn-refresh-tasks" id="btnRefreshTasks">鍒锋柊浠诲姟鐘舵€?/button>
</div>
<div class="task-list-wrap">
<ul class="task-list" id="taskList"></ul>
<div class="empty-tasks" id="emptyTasks">暂无任务</div>
<div class="empty-tasks" id="emptyTasks">鏆傛棤浠诲姟</div>
</div>
</div>
</div>
@@ -467,7 +467,7 @@
var cachedTasks = [];
var api = window.pywebview && window.pywebview.api;
// 顶部栏目切换:点击 nav-tab 时显示对应 data-panel tab-panel
// 椤堕儴鏍忕洰鍒囨崲锛氱偣鍑?nav-tab 鏃舵樉绀哄搴?data-panel 鐨?tab-panel
document.querySelectorAll('.nav-tab').forEach(function(tab) {
tab.addEventListener('click', function() {
var panelId = this.getAttribute('data-panel');
@@ -493,18 +493,18 @@
el.innerHTML = '';
return;
}
el.innerHTML = '已选 <b>' + selectedPaths.length + '</b> 个文件:<br>' +
el.innerHTML = '宸查€?<b>' + selectedPaths.length + '</b> 涓枃浠讹細<br>' +
selectedPaths.slice(0, 20).map(function(p) {
var name = p.split(/[/\\]/).pop();
return '<span title="' + p + '">' + name + '</span>';
}).join('') +
(selectedPaths.length > 20 ? '<span>… 等 ' + selectedPaths.length + ' 个</span>' : '');
(selectedPaths.length > 20 ? '<span>鈥?绛?' + selectedPaths.length + ' 涓?/span>' : '');
}
document.getElementById('btnSelectFiles').onclick = async function(e) {
e.stopPropagation();
if (!api || !api.select_brand_xlsx_files) {
showToast('当前环境不支持文件选择,请在本机客户端中打开');
showToast('褰撳墠鐜涓嶆敮鎸佹枃浠堕€夋嫨锛岃鍦ㄦ湰鏈哄鎴风涓墦寮€');
return;
}
try {
@@ -512,10 +512,10 @@
if (paths && paths.length) {
selectedPaths = paths;
renderSelected();
showToast('已选择 ' + paths.length + ' 个文件');
showToast('宸查€夋嫨 ' + paths.length + ' 涓枃浠?);
}
} catch (err) {
showToast('选择失败:' + (err.message || err));
showToast('閫夋嫨澶辫触锛? + (err.message || err));
}
};
@@ -525,16 +525,16 @@
try {
var res = await api.save_template_xlsx();
if (res && res.success) {
showToast('模板已保存至:' + (res.path || ''));
showToast('妯℃澘宸蹭繚瀛樿嚦锛? + (res.path || ''));
} else {
showToast(res && res.error ? res.error : '保存失败');
showToast(res && res.error ? res.error : '淇濆瓨澶辫触');
}
} catch (err) {
showToast('下载失败:' + (err.message || err));
showToast('涓嬭浇澶辫触锛? + (err.message || err));
}
return;
}
window.location.href = '/static/品牌文档格式_模板.xlsx';
window.location.href = '/static/鍝佺墝鏂囨。鏍煎紡_妯℃澘.xlsx';
};
document.getElementById('templateDownloadRow').onkeydown = function(e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); }
@@ -546,16 +546,16 @@
try {
var res = await api.save_template_zip();
if (res && res.success) {
showToast('模板已保存至:' + (res.path || ''));
showToast('妯℃澘宸蹭繚瀛樿嚦锛? + (res.path || ''));
} else {
showToast(res && res.error ? res.error : '保存失败');
showToast(res && res.error ? res.error : '淇濆瓨澶辫触');
}
} catch (err) {
showToast('下载失败:' + (err.message || err));
showToast('涓嬭浇澶辫触锛? + (err.message || err));
}
return;
}
window.location.href = '/static/模板2-以文件夹方式上传.zip';
window.location.href = '/static/妯℃澘2-浠ユ枃浠跺す鏂瑰紡涓婁紶.zip';
};
document.getElementById('templateDownloadRow2').onkeydown = function(e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); }
@@ -564,7 +564,7 @@
document.getElementById('btnSelectFolder').onclick =async function(e) {
e.stopPropagation();
if (!api || !api.select_brand_folder) {
showToast('当前环境不支持文件夹选择,请在本机客户端中打开');
showToast('褰撳墠鐜涓嶆敮鎸佹枃浠跺す閫夋嫨锛岃鍦ㄦ湰鏈哄鎴风涓墦寮€');
return;
}
try {
@@ -581,14 +581,14 @@
if (res.success && res.paths && res.paths.length) {
selectedPaths = res.paths;
renderSelected();
showToast('已选择文件夹内 ' + res.paths.length + ' xlsx 文件');
showToast('宸查€夋嫨鏂囦欢澶瑰唴 ' + res.paths.length + ' 涓?xlsx 鏂囦欢');
} else {
showToast(res.error || '该文件夹下没有 xlsx 文件');
showToast(res.error || '璇ユ枃浠跺す涓嬫病鏈?xlsx 鏂囦欢');
}
})
.catch(function() { showToast('请求失败'); });
.catch(function() { showToast('璇锋眰澶辫触'); });
} catch (err) {
showToast('选择失败:' + (err.message || err));
showToast('閫夋嫨澶辫触锛? + (err.message || err));
}
};
@@ -628,9 +628,9 @@
function onRunTaskFinished(status) {
stopPollAndResetRunUI();
loadTasks();
if (status === 'success') showToast('生成完成,可下载结果');
else if (status === 'failed') showToast('生成失败');
else if (status === 'cancelled') showToast('已取消');
if (status === 'success') showToast('鐢熸垚瀹屾垚锛屽彲涓嬭浇缁撴灉');
else if (status === 'failed') showToast('鐢熸垚澶辫触');
else if (status === 'cancelled') showToast('宸插彇娑?);
}
function pollRunTask(taskId) {
@@ -649,11 +649,11 @@
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.success) onRunTaskFinished('cancelled');
else showToast(res.error || '取消失败');
else showToast(res.error || '鍙栨秷澶辫触');
});
};
// 通过 SSE 订阅任务完成事件,完成后由后端主动推送,前端收到后立即隐藏进度条和取消按钮
// 閫氳繃 SSE 璁㈤槄浠诲姟瀹屾垚浜嬩欢锛屽畬鎴愬悗鐢卞悗绔富鍔ㄦ帹閫侊紝鍓嶇鏀跺埌鍚庣珛鍗抽殣钘忚繘搴︽潯鍜屽彇娑堟寜閽?
var eventsUrl = '/api/brand/tasks/' + taskId + '/events';
runTaskEventSource = new EventSource(eventsUrl);
runTaskEventSource.onmessage = function(e) {
@@ -685,18 +685,18 @@
if (runProgressText) {
var st = (t.status || '').toLowerCase();
if (st === 'running') {
runProgressText.textContent = total ? (cur + ' / ' + total) : '生成中…';
runProgressText.textContent = total ? (cur + ' / ' + total) : '鐢熸垚涓€?;
} else if (st === 'success') {
runProgressText.textContent = '已完成';
runProgressText.textContent = '宸插畬鎴?;
} else if (st === 'failed') {
runProgressText.textContent = '失败';
runProgressText.textContent = '澶辫触';
} else if (st === 'cancelled') {
runProgressText.textContent = '已取消';
runProgressText.textContent = '宸插彇娑?;
} else {
runProgressText.textContent = '执行中…';
runProgressText.textContent = '鎵ц?;
}
}
// 行级进度:从内存接口 /api/brand/tasks/<id>/line-progress 拉取
// 琛岀骇杩涘害锛氫粠鍐呭瓨鎺ュ彛 /api/brand/tasks/<id>/line-progress 鎷夊彇
if (runLineProgressWrap && runLineProgressFill && runLineProgressText) {
var st2 = (t.status || '').toLowerCase();
if (st2 === 'running') {
@@ -715,14 +715,17 @@
var curLine = info.current_line || 0;
runLineProgressWrap.style.display = 'block';
runLineProgressFill.style.width = totalLines ? (100 * curLine / totalLines) + '%' : '0%';
runLineProgressText.textContent = totalLines ? (curLine + ' / ' + totalLines) : '当前文件处理中…';
runLineProgressText.textContent = totalLines ? (curLine + ' / ' + totalLines) : '褰撳墠鏂囦欢澶勭悊涓€?;
})
.catch(function() {});
.catch(function(err) {
console.error('loadTasks failed:', err);
showToast('浠诲姟鍒楄鍒锋柊澶辫触');
});
} else {
runLineProgressWrap.style.display = 'none';
}
}
// 终态也通过轮询兜底,确保 UI 一定被重置SSE 可能因网络未收到)
// 缁堟€佷篃閫氳繃杞鍏滃簳锛岀‘淇?UI 涓€瀹氳閲嶇疆锛圫SE 鍙兘鍥犵綉缁滄湭鏀跺埌锛?
var st = (t.status || '').toLowerCase();
if (st === 'success' || st === 'failed' || st === 'cancelled') {
onRunTaskFinished(st);
@@ -735,7 +738,7 @@
document.getElementById('btnRun').onclick = function() {
if (selectedPaths.length === 0) {
showToast('请先选择 Excel 文件或文件夹');
showToast('璇峰厛閫夋嫨 Excel 鏂囦欢鎴栨枃浠跺す');
return;
}
var mode = getRunMode();
@@ -755,16 +758,17 @@
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.success && res.task_id) {
loadTasks();
loadingMsg.style.display = 'none';
pollRunTask(res.task_id);
} else {
stopPollAndResetRunUI();
showToast(res.error || '运行失败');
showToast(res.error || '杩愯澶辫触');
}
})
.catch(function() {
stopPollAndResetRunUI();
showToast('请求失败或超时');
showToast('璇锋眰澶辫触鎴栬秴鏃?);
});
} else {
fetch('/api/brand/tasks', {
@@ -776,13 +780,13 @@
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.success) {
showToast('任务已添加,请在任务队列查看');
showToast('浠诲姟宸叉坊鍔狅紝璇峰湪浠诲姟闃熷垪鏌ョ湅');
loadTasks();
} else {
showToast(res.error || '添加失败');
showToast(res.error || '娣诲姞澶辫触');
}
})
.catch(function() { showToast('请求失败'); });
.catch(function() { showToast('璇锋眰澶辫触'); });
}
};
@@ -801,8 +805,8 @@
cachedTasks = res.items;
list.innerHTML = res.items.map(function(t) {
var statusClass = (t.status || 'pending').toLowerCase();
var statusText = { pending: '等待中', running: '执行中', success: '已完成', failed: '失败', cancelled: '已取消' }[t.status] || t.status;
var filesDesc = (t.file_paths && t.file_paths.length) ? t.file_paths.length + ' 个文件' : '';
var statusText = { pending: '绛夊緟涓?, running: '鎵ц?, success: '宸插畬鎴?, failed: '澶辫触', cancelled: '宸插彇娑? }[t.status] || t.status;
var filesDesc = (t.file_paths && t.file_paths.length) ? t.file_paths.length + ' 涓枃浠? : '';
var total = t.progress_total || 0;
var cur = t.progress_current || 0;
var pct = total ? Math.min(100, (100 * cur / total)) : 0;
@@ -811,13 +815,13 @@
: '';
var hasZipUrl = t.result_paths && t.result_paths.zip_url;
var downloadHtml = hasZipUrl
? '<a class="download js-save-zip" href="#" data-task-id="' + t.id + '">下载结果</a>'
? '<a class="download js-save-zip" href="#" data-task-id="' + t.id + '">涓嬭浇缁撴灉</a>'
: '<span class="download hide"></span>';
var cancelBtn = statusClass === 'running'
? '<button type="button" class="btn-cancel js-cancel-task" data-task-id="' + t.id + '">取消</button>'
? '<button type="button" class="btn-cancel js-cancel-task" data-task-id="' + t.id + '">鍙栨秷</button>'
: '';
var deleteBtn = '<button type="button" class="btn-delete js-delete-task" data-task-id="' + t.id + '">删除</button>';
var titleDesc = (t.desc && String(t.desc).trim()) ? String(t.desc).trim() : ('任务 #' + t.id);
var deleteBtn = '<button type="button" class="btn-delete js-delete-task" data-task-id="' + t.id + '">鍒犻櫎</button>';
var titleDesc = (t.desc && String(t.desc).trim()) ? String(t.desc).trim() : ('浠诲姟 #' + t.id);
var displayDesc = titleDesc.length > 20 ? titleDesc.substring(0, 20) + '....' : titleDesc;
return '<li class="task-item">' +
'<div class="left">' +
@@ -834,15 +838,18 @@
'</div></li>';
}).join('');
})
.catch(function() {});
.catch(function(err) {
console.error('loadTasks failed:', err);
showToast('浠诲姟鍒楄〃鍒锋柊澶辫触');
});
}
// 手动刷新任务状态按钮
// 鎵嬪姩鍒锋柊浠诲姟鐘舵€佹寜閽?
var btnRefreshTasks = document.getElementById('btnRefreshTasks');
if (btnRefreshTasks) {
btnRefreshTasks.onclick = function () {
loadTasks();
showToast('任务状态已刷新');
showToast('浠诲姟鐘舵€佸凡鍒锋柊');
};
}
@@ -857,7 +864,7 @@
fetch('/api/brand/tasks/' + taskId + '/cancel', { method: 'POST', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.success) { showToast('已取消'); loadTasks(); } else { showToast(res.error || '取消失败'); }
if (res.success) { showToast('宸插彇娑?); loadTasks(); } else { showToast(res.error || '鍙栨秷澶辫触'); }
});
return;
}
@@ -867,7 +874,7 @@
fetch('/api/brand/tasks/' + taskId, { method: 'DELETE', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.success) { showToast('已删除'); loadTasks(); } else { showToast(res.error || '删除失败'); }
if (res.success) { showToast('宸插垹闄?); loadTasks(); } else { showToast(res.error || '鍒犻櫎澶辫触'); }
});
return;
}
@@ -875,7 +882,7 @@
if (!a || !a.classList || !a.classList.contains('js-save-zip')) return;
var task = cachedTasks.filter(function(t) { return String(t.id) === String(taskId); })[0];
if (!task || !task.result_paths || !task.result_paths.zip_url) {
showToast('无法获取下载地址');
showToast('鏃犳硶鑾峰彇涓嬭浇鍦板潃');
return;
}
var zipUrl = task.result_paths.zip_url;
@@ -884,21 +891,21 @@
a.style.pointerEvents = 'none';
var done = function(ret) {
if (ret && ret.success) {
showToast('已保存:' + (ret.path || filename));
} else if (ret && ret.error && ret.error !== '用户取消') {
showToast('保存失败:' + ret.error);
showToast('宸蹭繚瀛橈細' + (ret.path || filename));
} else if (ret && ret.error && ret.error !== '鐢ㄦ埛鍙栨秷') {
showToast('淇濆瓨澶辫触锛? + ret.error);
}
a.style.pointerEvents = '';
};
try {
var ret = api.save_file_from_url(zipUrl, filename);
if (ret && typeof ret.then === 'function') {
ret.then(done).catch(function(err) { showToast('保存失败:' + (err && err.message || err)); a.style.pointerEvents = ''; });
ret.then(done).catch(function(err) { showToast('淇濆瓨澶辫触锛? + (err && err.message || err)); a.style.pointerEvents = ''; });
} else {
done(ret);
}
} catch (err) {
showToast('保存失败:' + (err.message || err));
showToast('淇濆瓨澶辫触锛? + (err.message || err));
a.style.pointerEvents = '';
}
} else {
@@ -907,7 +914,7 @@
});
loadTasks();
// setInterval(loadTasks, 10000);
setInterval(loadTasks, 5000);
if (window.addEventListener) {
window.addEventListener('pywebviewready', function() {
@@ -919,3 +926,4 @@
</script>
</body>
</html>

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 {
@@ -186,7 +186,7 @@
</head>
<body data-user-id="{{ user_id or '' }}">
<header class="header">
<span class="header-title">南日AI</span>
<span class="header-title">数富AI</span>
<div class="header-right">
{% if is_admin %}
<!-- <a href="/admin" class="admin-link">用户管理</a>-->
@@ -219,6 +219,7 @@
<script>
// 权限校验:根据 column-permissions 接口按 column_key 显示入口
(function() {
localStorage.setItem("uid",{{ user_id }})
var uid = document.body.getAttribute('data-user-id');
if (!uid) return;
var baseUrl = window.location.origin;