388 lines
16 KiB
HTML
388 lines
16 KiB
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>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", sans-serif;
|
||
background: linear-gradient(rgba(255,255,255,0.5), rgba(255,255,255,0.5)),
|
||
url("/static/bg.jpg") center/cover no-repeat;;
|
||
/*background-image: url("/static/bg.jpg");*/
|
||
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px;
|
||
}
|
||
.header {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 56px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 24px;
|
||
background: rgba(255,255,255,0.5);
|
||
}
|
||
.header-title { font-size: 18px; font-weight: 600; color: #333; }
|
||
.header-right { display: flex; align-items: center; gap: 16px; position: relative; }
|
||
.header-right a, .header-right span {
|
||
font-size: 14px; color: #555; text-decoration: none;
|
||
}
|
||
.header-right a:hover { color: #667eea; }
|
||
.entrances {
|
||
display: flex;
|
||
gap: 32px;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
.entrance-btn {
|
||
width: 160px;
|
||
height: 100px;
|
||
background: #c5c1c1;
|
||
border: 4px solid #b8d4e3;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-decoration: none;
|
||
}
|
||
.entrance-btn:hover {
|
||
background: #f8fbfd;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||
}
|
||
.entrance-btn.wb { cursor: pointer; }
|
||
.entrance-btn.disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.9;
|
||
}
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 40px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background: rgba(0,0,0,0.75);
|
||
color: #fff;
|
||
padding: 12px 24px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
pointer-events: none;
|
||
}
|
||
.toast.show { opacity: 1; }
|
||
.admin-link {
|
||
font-size: 13px;
|
||
color: #888;
|
||
}
|
||
.update-section {
|
||
margin-top: 48px;
|
||
padding: 20px 24px;
|
||
background: rgba(255,255,255,0.85);
|
||
border-radius: 12px;
|
||
border: 1px solid rgba(0,0,0,0.06);
|
||
max-width: 420px;
|
||
}
|
||
.update-section-title {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.update-block {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
}
|
||
.update-version-text {
|
||
font-size: 13px;
|
||
color: #555;
|
||
}
|
||
.btn-check-update {
|
||
padding: 8px 14px;
|
||
font-size: 13px;
|
||
background: #667eea;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
}
|
||
.btn-check-update:hover {
|
||
background: #5a6fd6;
|
||
}
|
||
.update-hint {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 8px;
|
||
min-height: 18px;
|
||
}
|
||
.btn-download-update {
|
||
margin-top: 8px;
|
||
padding: 8px 14px;
|
||
font-size: 13px;
|
||
background: #28a745;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
}
|
||
.btn-download-update:hover {
|
||
background: #218838;
|
||
}
|
||
.header-update-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: rgba(102,126,234,0.12);
|
||
color: #4c5bd4;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 15px;
|
||
padding: 0;
|
||
}
|
||
.header-update-btn:hover {
|
||
background: rgba(102,126,234,0.2);
|
||
}
|
||
.header-update-panel {
|
||
position: absolute;
|
||
top: 44px;
|
||
right: 0;
|
||
width: 280px;
|
||
padding: 14px 16px;
|
||
background: rgba(255,255,255,0.98);
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
||
border: 1px solid rgba(0,0,0,0.04);
|
||
z-index: 10;
|
||
display: none;
|
||
}
|
||
.header-update-title {
|
||
font-size: 13px;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body data-user-id="{{ user_id or '' }}">
|
||
<header class="header">
|
||
<span class="header-title">数富AI</span>
|
||
<div class="header-right">
|
||
{% if is_admin %}
|
||
<!-- <a href="/admin" class="admin-link">用户管理</a>-->
|
||
{% endif %}
|
||
<span>{{ username }}</span>
|
||
<button type="button" class="header-update-btn" id="homeUpdateToggle" title="检测更新">↻</button>
|
||
<a href="/logout" onclick="return handleLogout(event);">退出</a>
|
||
<div class="header-update-panel" id="homeUpdatePanel">
|
||
<div class="header-update-title"><span>↻</span> 软件更新</div>
|
||
<div class="update-block">
|
||
<span class="update-version-text" id="homeVersionText">当前版本: {{ version }}</span>
|
||
<button type="button" class="btn-check-update" id="homeBtnCheckUpdate"><span>↻</span> 检测</button>
|
||
</div>
|
||
<div class="update-hint" id="homeUpdateHint"></div>
|
||
<div style="margin-top: 4px;">
|
||
<button type="button" class="btn-download-update" id="homeBtnDownloadUpdate" style="display: none;">立即更新</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="entrances" id="entrances">
|
||
<a class="entrance-btn" href="/brand" data-column-key="brand">亚马逊</a>
|
||
<a class="entrance-btn disabled" href="javascript:;" data-msg="暂未开通,敬请期待" data-column-key="wb">wildberries</a>
|
||
<a class="entrance-btn image" href="/image" data-column-key="image">图片</a>
|
||
</div>
|
||
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script>
|
||
function getAppPermissionCacheKey(uid) {
|
||
return 'app_column_permissions:' + String(uid || '');
|
||
}
|
||
// 权限校验:根据 Java column-permissions 接口按 column_key 显示入口
|
||
(function() {
|
||
localStorage.setItem("uid",{{ user_id }})
|
||
var uid = document.body.getAttribute('data-user-id');
|
||
if (!uid) return;
|
||
var cacheKey = getAppPermissionCacheKey(uid);
|
||
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
|
||
var JAVA_API_BASE = "{{ java_api_base or '' }}".replace(/\/+$/, '');
|
||
var token = '';
|
||
try { token = localStorage.getItem(AUTH_TOKEN_KEY) || ''; } catch (e) {}
|
||
if (!JAVA_API_BASE || !token) return;
|
||
var apiUrl = JAVA_API_BASE + '/api/admin/permission-users/' + uid + '/column-permissions?menuType=app';
|
||
fetch(apiUrl, {
|
||
credentials: 'include',
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(res) {
|
||
if (!res || !res.success || !Array.isArray(res.data)) return;
|
||
try {
|
||
localStorage.setItem(cacheKey, JSON.stringify(res.data));
|
||
} catch (e) {}
|
||
var allowedKeys = res.data.map(function(item) { return (item.columnKey || item.column_key || '').toLowerCase(); });
|
||
document.querySelectorAll('.entrances .entrance-btn[data-column-key]').forEach(function(btn) {
|
||
var key = (btn.getAttribute('data-column-key') || '').toLowerCase();
|
||
btn.style.display = allowedKeys.indexOf(key) >= 0 ? '' : 'none';
|
||
});
|
||
})
|
||
.catch(function() {});
|
||
})();
|
||
|
||
document.querySelectorAll('.entrance-btn.disabled').forEach(function(btn) {
|
||
btn.onclick = function(e) {
|
||
e.preventDefault();
|
||
var msg = btn.getAttribute('data-msg') || '暂未开通,敬请期待';
|
||
var t = document.getElementById('toast');
|
||
t.textContent = msg;
|
||
t.classList.add('show');
|
||
setTimeout(function() { t.classList.remove('show'); }, 2000);
|
||
};
|
||
});
|
||
|
||
function handleLogout(ev) {
|
||
if (ev && ev.preventDefault) ev.preventDefault();
|
||
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
|
||
var JAVA_API_BASE = "{{ java_api_base or '' }}".replace(/\/+$/, '');
|
||
var token = '';
|
||
try { token = localStorage.getItem(AUTH_TOKEN_KEY) || ''; } catch (e) {}
|
||
|
||
function cleanLocal() {
|
||
try {
|
||
localStorage.removeItem('maixiang_api_key');
|
||
localStorage.removeItem(AUTH_TOKEN_KEY);
|
||
var uid = document.body.getAttribute('data-user-id');
|
||
if (uid) {
|
||
localStorage.removeItem(getAppPermissionCacheKey(uid));
|
||
}
|
||
} catch (e) { console.log(e); }
|
||
}
|
||
|
||
function gotoPythonLogout() {
|
||
cleanLocal();
|
||
window.location.href = '/logout';
|
||
}
|
||
|
||
if (!JAVA_API_BASE || !token) {
|
||
gotoPythonLogout();
|
||
return false;
|
||
}
|
||
// 先让 Java 端清 cookie/会话,再走 Python /logout 清 Python cookie
|
||
var headers = { 'Authorization': 'Bearer ' + token };
|
||
try {
|
||
fetch(JAVA_API_BASE + '/logout', {
|
||
method: 'POST',
|
||
credentials: 'include',
|
||
headers: headers
|
||
}).then(gotoPythonLogout).catch(gotoPythonLogout);
|
||
} catch (e) {
|
||
gotoPythonLogout();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 软件更新(与 index 设置中逻辑一致)
|
||
(function() {
|
||
const versionText = document.getElementById('homeVersionText');
|
||
const updateHint = document.getElementById('homeUpdateHint');
|
||
const btnDownload = document.getElementById('homeBtnDownloadUpdate');
|
||
const updatePanel = document.getElementById('homeUpdatePanel');
|
||
const toggleBtn = document.getElementById('homeUpdateToggle');
|
||
if (!versionText || !updateHint || !btnDownload || !updatePanel || !toggleBtn) return;
|
||
|
||
let currentVersion = '{{version}}';
|
||
versionText.textContent = '当前版本: v' + currentVersion;
|
||
|
||
// 打开/关闭浮层
|
||
toggleBtn.addEventListener('click', function(e) {
|
||
e.stopPropagation();
|
||
const isVisible = updatePanel.style.display === 'block';
|
||
updatePanel.style.display = isVisible ? 'none' : 'block';
|
||
});
|
||
|
||
// 点击外部关闭浮层
|
||
document.addEventListener('click', function() {
|
||
updatePanel.style.display = 'none';
|
||
});
|
||
updatePanel.addEventListener('click', function(e) {
|
||
e.stopPropagation();
|
||
});
|
||
|
||
document.getElementById('homeBtnCheckUpdate').onclick = async function() {
|
||
updateHint.textContent = '正在检测更新...';
|
||
btnDownload.style.display = 'none';
|
||
try {
|
||
const resp = await fetch('/api/version', { credentials: 'same-origin' }).catch(function() { return null; });
|
||
if (resp && resp.ok) {
|
||
const data = await resp.json();
|
||
currentVersion = (data.version || currentVersion).replace(/^v/i, '');
|
||
versionText.textContent = '当前版本: v' + currentVersion;
|
||
if (data.has_update && data.latest_version) {
|
||
updateHint.textContent = '发现新版本 v' + data.latest_version + (data.desc ? ':' + data.desc : '');
|
||
btnDownload.style.display = 'inline-block';
|
||
btnDownload.textContent = '立即更新';
|
||
btnDownload.onclick = async function() {
|
||
if (!confirm('有更新,是否现在更新?\n更新将下载安装包并重启程序。')) return;
|
||
if (!data.file_url) {
|
||
updateHint.textContent = '暂无下载地址,请关注官方渠道。';
|
||
return;
|
||
}
|
||
updateHint.textContent = '正在下载并准备更新,程序将自动退出...';
|
||
btnDownload.disabled = true;
|
||
try {
|
||
const updateResp = await fetch('/api/update/do', {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ file_url: data.file_url })
|
||
});
|
||
const result = await updateResp.json().catch(function() { return {}; });
|
||
if (result.success) {
|
||
updateHint.textContent = '更新已启动,程序即将退出...';
|
||
} else {
|
||
updateHint.textContent = result.error || '更新启动失败';
|
||
btnDownload.disabled = false;
|
||
}
|
||
} catch (e) {
|
||
updateHint.textContent = '请求更新失败,请重试';
|
||
btnDownload.disabled = false;
|
||
}
|
||
};
|
||
} else {
|
||
updateHint.textContent = '已是最新版本';
|
||
}
|
||
} else {
|
||
updateHint.textContent = '无法连接更新服务,请稍后重试。';
|
||
}
|
||
} catch (e) {
|
||
updateHint.textContent = '检测更新失败';
|
||
}
|
||
};
|
||
btnDownload.style.display = 'none';
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|