Files
zjpb.net/templates/admin/seo_tools.html
Jowe 2eefaa8cc9 feat: v3.2 - 用户管理功能和后台菜单统一
新增功能:
- 用户管理列表页面(搜索、分页)
- 用户详情页面(基本信息、收藏统计)
- 管理员重置用户密码功能
- 管理员修改用户昵称功能
- 管理后台首页添加用户统计卡片

优化改进:
- 统一后台菜单结构,创建可复用的 sidebar 组件
- 所有后台页面使用统一菜单,避免硬编码
- 优化权限配置文件,清理冗余规则

技术文档:
- 添加任务分解规则文档
- 添加后台菜单统一规则文档
- 添加数据库字段修复脚本

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 23:20:35 +08:00

482 lines
18 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">
{% set active_page = 'seo' %}
{% include 'admin/components/sidebar.html' %}
<!-- 右侧主内容区 -->
<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>