Files
zjpb.net/templates/admin/seo_tools.html
Jowe c68d846daa fix: 修复SEO工具页面模板继承错误,改为独立完整HTML页面
- 将seo_tools.html从继承admin/master.html改为独立完整HTML
- 从master.html中移除SEO工具菜单项,避免url_for错误
- 修复本地UndefinedError: 'admin_view' is undefined错误
2026-01-03 23:26:25 +08:00

575 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SEO工具管理 - ZJPB - 自己品吧</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Google Material Symbols -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom Admin Theme -->
<link href="{{ url_for('static', filename='css/admin-sidebar.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/admin-actions.css') }}" rel="stylesheet">
</head>
<body class="admin-sidebar-layout">
<!-- 左侧菜单栏 -->
<aside class="admin-sidebar">
<!-- Logo -->
<div class="sidebar-logo">
<span class="material-symbols-outlined logo-icon">blur_on</span>
<span class="logo-text">ZJPB - 自己品吧</span>
</div>
<!-- 主菜单 -->
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">主菜单</div>
<ul class="nav-menu">
<li class="nav-item">
<a href="{{ url_for('admin.index') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">dashboard</span>
<span class="nav-text">控制台</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('site.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">public</span>
<span class="nav-text">网站管理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('tag.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">label</span>
<span class="nav-text">标签管理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('news.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">newspaper</span>
<span class="nav-text">新闻管理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('admin_users.index_view') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">admin_panel_settings</span>
<span class="nav-text">管理员</span>
</a>
</li>
</ul>
</div>
<!-- 系统菜单 -->
<div class="nav-section">
<div class="nav-section-title">系统</div>
<ul class="nav-menu">
<li class="nav-item active">
<a href="{{ url_for('seo_tools') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">search</span>
<span class="nav-text">SEO工具</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('batch_import') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">upload_file</span>
<span class="nav-text">批量导入</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('change_password') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">lock_reset</span>
<span class="nav-text">修改密码</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('index') }}" class="nav-link" target="_blank">
<span class="material-symbols-outlined nav-icon">open_in_new</span>
<span class="nav-text">查看网站</span>
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('admin_logout') }}" class="nav-link">
<span class="material-symbols-outlined nav-icon">logout</span>
<span class="nav-text">退出登录</span>
</a>
</li>
</ul>
</div>
</nav>
<!-- 用户信息 -->
<div class="sidebar-user">
<div class="user-avatar">
<span class="material-symbols-outlined">account_circle</span>
</div>
<div class="user-info">
<div class="user-name">{{ current_user.username }}</div>
<div class="user-email">{{ current_user.email or 'admin@zjpb.com' }}</div>
</div>
</div>
</aside>
<!-- 右侧主内容区 -->
<div class="admin-main">
<!-- 顶部导航栏 -->
<header class="admin-header">
<div class="header-breadcrumb">
<a href="{{ url_for('admin.index') }}" class="breadcrumb-link">控制台</a>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">SEO工具管理</span>
</div>
<div class="header-actions">
<div class="search-box">
<span class="material-symbols-outlined search-icon">search</span>
<input type="text" placeholder="全局搜索..." class="search-input">
</div>
<button class="header-btn">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="header-btn">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
<!-- 页面内容 -->
<main class="admin-content">
<div class="page-header">
<div>
<h1 class="page-title">
<i class="fa fa-search"></i> SEO工具管理
</h1>
<p class="page-description">管理sitemap、通知搜索引擎更新</p>
</div>
</div>
<!-- Sitemap状态卡片 -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fa fa-sitemap"></i> Sitemap状态
</h5>
</div>
<div class="card-body">
{% if sitemap_info.exists %}
<div class="alert alert-success">
<i class="fa fa-check-circle"></i> 静态sitemap.xml已生成
</div>
<table class="table table-sm">
<tr>
<th width="40%">最后更新时间:</th>
<td>{{ sitemap_info.last_updated }}</td>
</tr>
<tr>
<th>文件大小:</th>
<td>{{ (sitemap_info.size / 1024) | round(2) }} KB</td>
</tr>
<tr>
<th>文件路径:</th>
<td><code>static/sitemap.xml</code></td>
</tr>
</table>
{% else %}
<div class="alert alert-warning">
<i class="fa fa-exclamation-triangle"></i> 静态sitemap.xml尚未生成
</div>
<p class="text-muted mb-0">点击下方按钮生成静态sitemap文件</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
<i class="fa fa-globe"></i> 动态Sitemap
</h5>
</div>
<div class="card-body">
<p>动态sitemap路由始终可用</p>
<div class="input-group mb-3">
<input type="text" class="form-control" value="{{ request.url_root }}sitemap.xml" readonly>
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('{{ request.url_root }}sitemap.xml')">
<i class="fa fa-copy"></i> 复制
</button>
</div>
<p class="text-muted small mb-0">
<i class="fa fa-info-circle"></i> 动态sitemap实时反映数据库内容无需手动生成
</p>
</div>
</div>
</div>
</div>
<!-- 操作按钮区 -->
<div class="row mb-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-dark text-white">
<h5 class="mb-0">
<i class="fa fa-tools"></i> 快速操作
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<h6><i class="fa fa-file-code"></i> 生成静态Sitemap</h6>
<p class="text-muted small">生成static/sitemap.xml文件可用于静态服务或下载</p>
<button id="generateSitemapBtn" class="btn btn-success btn-lg w-100">
<i class="fa fa-cog"></i> 生成静态sitemap.xml
</button>
</div>
<div class="col-md-6 mb-3">
<h6><i class="fa fa-bell"></i> 通知搜索引擎</h6>
<p class="text-muted small">向Google、Baidu、Bing提交sitemap更新通知</p>
<button id="notifyEnginesBtn" class="btn btn-primary btn-lg w-100">
<i class="fa fa-paper-plane"></i> 通知搜索引擎更新
</button>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<button id="doAllBtn" class="btn btn-warning btn-lg w-100">
<i class="fa fa-rocket"></i> 一键生成并通知(推荐)
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 操作结果显示区 -->
<div class="row">
<div class="col-md-12">
<div id="resultArea" class="card" style="display: none;">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0">
<i class="fa fa-history"></i> 操作结果
</h5>
</div>
<div class="card-body" id="resultContent">
<!-- 动态填充内容 -->
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
<style>
.page-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.page-description {
color: #666;
margin-bottom: 24px;
}
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.btn-lg {
padding: 12px 24px;
font-size: 16px;
}
#resultArea {
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.result-item {
padding: 12px;
border-left: 4px solid #ddd;
margin-bottom: 10px;
background: #f8f9fa;
}
.result-item.success {
border-left-color: #28a745;
background: #d4edda;
}
.result-item.error {
border-left-color: #dc3545;
background: #f8d7da;
}
.result-item.warning {
border-left-color: #ffc107;
background: #fff3cd;
}
</style>
<script>
// 复制到剪贴板
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
alert('已复制到剪贴板!');
});
}
// 禁用按钮
function disableButtons() {
document.querySelectorAll('#generateSitemapBtn, #notifyEnginesBtn, #doAllBtn').forEach(btn => {
btn.disabled = true;
});
}
// 启用按钮
function enableButtons() {
document.querySelectorAll('#generateSitemapBtn, #notifyEnginesBtn, #doAllBtn').forEach(btn => {
btn.disabled = false;
});
}
// 显示结果
function showResult(html) {
const resultArea = document.getElementById('resultArea');
const resultContent = document.getElementById('resultContent');
resultContent.innerHTML = html;
resultArea.style.display = 'block';
resultArea.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// 生成静态sitemap
document.getElementById('generateSitemapBtn').addEventListener('click', async function() {
disableButtons();
const originalText = this.innerHTML;
this.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 生成中...';
try {
const response = await fetch('/api/generate-static-sitemap', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
showResult(`
<div class="result-item success">
<h5><i class="fa fa-check-circle"></i> 生成成功</h5>
<p>${data.message}</p>
<table class="table table-sm mt-2">
<tr><th>URL数量</th><td>${data.total_urls}</td></tr>
<tr><th>文件路径:</th><td><code>${data.file_path}</code></td></tr>
<tr><th>生成时间:</th><td>${data.timestamp}</td></tr>
</table>
</div>
`);
// 刷新页面以更新sitemap状态
setTimeout(() => location.reload(), 2000);
} else {
showResult(`
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 生成失败</h5>
<p>${data.message}</p>
</div>
`);
}
} catch (error) {
showResult(`
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 请求失败</h5>
<p>网络错误: ${error.message}</p>
</div>
`);
} finally {
this.innerHTML = originalText;
enableButtons();
}
});
// 通知搜索引擎
document.getElementById('notifyEnginesBtn').addEventListener('click', async function() {
disableButtons();
const originalText = this.innerHTML;
this.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 通知中...';
try {
const response = await fetch('/api/notify-search-engines', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
let resultsHtml = `
<div class="result-item success">
<h5><i class="fa fa-check-circle"></i> ${data.message}</h5>
<p>Sitemap URL: <code>${data.sitemap_url}</code></p>
</div>
`;
data.results.forEach(result => {
const statusClass = result.status === 'success' ? 'success' : 'error';
const icon = result.status === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
resultsHtml += `
<div class="result-item ${statusClass}">
<h6><i class="fa ${icon}"></i> ${result.engine}</h6>
<p>${result.message}</p>
</div>
`;
});
showResult(resultsHtml);
} else {
showResult(`
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 通知失败</h5>
<p>${data.message}</p>
</div>
`);
}
} catch (error) {
showResult(`
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 请求失败</h5>
<p>网络错误: ${error.message}</p>
</div>
`);
} finally {
this.innerHTML = originalText;
enableButtons();
}
});
// 一键生成并通知
document.getElementById('doAllBtn').addEventListener('click', async function() {
disableButtons();
const originalText = this.innerHTML;
this.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 处理中...';
let allResultsHtml = '';
try {
// Step 1: 生成sitemap
const sitemapResponse = await fetch('/api/generate-static-sitemap', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const sitemapData = await sitemapResponse.json();
if (sitemapData.success) {
allResultsHtml += `
<div class="result-item success">
<h5><i class="fa fa-check-circle"></i> 步骤1生成sitemap成功</h5>
<p>${sitemapData.message}</p>
</div>
`;
// Step 2: 通知搜索引擎
const notifyResponse = await fetch('/api/notify-search-engines', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const notifyData = await notifyResponse.json();
if (notifyData.success) {
allResultsHtml += `
<div class="result-item success">
<h5><i class="fa fa-check-circle"></i> 步骤2${notifyData.message}</h5>
</div>
`;
notifyData.results.forEach(result => {
const statusClass = result.status === 'success' ? 'success' : 'warning';
const icon = result.status === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
allResultsHtml += `
<div class="result-item ${statusClass}">
<h6><i class="fa ${icon}"></i> ${result.engine}</h6>
<p>${result.message}</p>
</div>
`;
});
} else {
allResultsHtml += `
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 步骤2通知搜索引擎失败</h5>
<p>${notifyData.message}</p>
</div>
`;
}
} else {
allResultsHtml += `
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 步骤1生成sitemap失败</h5>
<p>${sitemapData.message}</p>
</div>
`;
}
showResult(allResultsHtml);
// 如果全部成功2秒后刷新页面
if (sitemapData.success) {
setTimeout(() => location.reload(), 3000);
}
} catch (error) {
showResult(`
<div class="result-item error">
<h5><i class="fa fa-times-circle"></i> 操作失败</h5>
<p>网络错误: ${error.message}</p>
</div>
`);
} finally {
this.innerHTML = originalText;
enableButtons();
}
});
</script>
</body>
</html>