功能特性: - 前端文章展示 (Markdown渲染、代码高亮) - 后台管理系统 (文章、分类、标签、作者管理) - 图片上传 (点击、拖拽、粘贴) - 网站设置 (Logo、底部宣传图片上传) - 访问统计 (IP记录、趋势图表) - 参考来源、附件支持 - 单端口部署 (16012) 技术栈: - Flask 3.0 + SQLAlchemy - SQLite数据库 - marked.js + highlight.js (Markdown渲染) - Chart.js (统计图表)
422 lines
15 KiB
HTML
422 lines
15 KiB
HTML
{% extends 'admin/base.html' %}
|
||
|
||
{% block title %}网站设置{% endblock %}
|
||
{% block page_title %}网站设置{% endblock %}
|
||
|
||
{% block content %}
|
||
<form method="POST" class="settings-form" enctype="multipart/form-data">
|
||
<div class="settings-section">
|
||
<h3>基本信息</h3>
|
||
|
||
<div class="form-group">
|
||
<label for="site_name">网站名称</label>
|
||
<input type="text" id="site_name" name="site_name"
|
||
value="{{ config.site_name }}" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="site_subtitle">网站副标题</label>
|
||
<input type="text" id="site_subtitle" name="site_subtitle"
|
||
value="{{ config.site_subtitle or '' }}"
|
||
placeholder="显示在名称旁边的简短描述">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="site_logo">网站Logo</label>
|
||
<div class="image-upload-wrapper">
|
||
<input type="url" id="site_logo" name="site_logo"
|
||
value="{{ config.site_logo or '' }}"
|
||
placeholder="Logo图片URL">
|
||
<div class="upload-btn-wrapper">
|
||
<button type="button" class="btn btn-sm btn-secondary" onclick="document.getElementById('logo_file').click()">上传图片</button>
|
||
<input type="file" id="logo_file" name="logo_file" accept="image/*" style="display:none" onchange="handleUpload(this, 'site_logo')">
|
||
</div>
|
||
</div>
|
||
{% if config.site_logo %}
|
||
<div class="image-preview">
|
||
<img src="{{ config.site_logo }}" alt="Logo预览" style="max-height: 50px;">
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h3>SEO设置</h3>
|
||
|
||
<div class="form-group">
|
||
<label for="site_keywords">网站关键词</label>
|
||
<input type="text" id="site_keywords" name="site_keywords"
|
||
value="{{ config.site_keywords or '' }}"
|
||
placeholder="技术博客,Python,开发,编程">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="site_description">网站描述</label>
|
||
<textarea id="site_description" name="site_description" rows="3"
|
||
placeholder="分享技术知识,记录成长历程">{{ config.site_description or '' }}</textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h3>底部信息</h3>
|
||
|
||
<div class="form-group">
|
||
<label for="footer_text">底部主文字</label>
|
||
<input type="text" id="footer_text" name="footer_text"
|
||
value="{{ config.footer_text or '' }}"
|
||
placeholder="© 2024 技术博客">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="footer_copyright">版权信息</label>
|
||
<input type="text" id="footer_copyright" name="footer_copyright"
|
||
value="{{ config.footer_copyright or '' }}"
|
||
placeholder="Copyright © 2024 All Rights Reserved">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="footer_technology">技术支持说明</label>
|
||
<input type="text" id="footer_technology" name="footer_technology"
|
||
value="{{ config.footer_technology or '' }}"
|
||
placeholder="Powered by Flask">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h3>底部宣传图片</h3>
|
||
<small class="form-hint">可在页面底部右侧展示宣传图片、二维码等,支持上传或输入URL</small>
|
||
|
||
<div class="promo-images-grid">
|
||
<!-- 宣传图片1 -->
|
||
<div class="promo-image-item">
|
||
<h4>图片 1</h4>
|
||
<div class="form-group">
|
||
<label>图片</label>
|
||
<div class="image-upload-box" onclick="document.getElementById('promo_image_1_file').click()">
|
||
<input type="file" id="promo_image_1_file" name="promo_image_1_file" accept="image/*" onchange="previewUpload(this, 'preview1', 'promo_image_1')">
|
||
<input type="hidden" id="promo_image_1" name="promo_image_1" value="{{ config.promo_image_1 or '' }}">
|
||
{% if config.promo_image_1 %}
|
||
<div id="preview1" class="upload-preview-has-image">
|
||
<img src="{{ config.promo_image_1 }}" alt="图片1">
|
||
<button type="button" class="remove-btn" onclick="removeImage('preview1', 'promo_image_1')">×</button>
|
||
</div>
|
||
{% else %}
|
||
<div id="preview1" class="upload-preview-empty">
|
||
<span>点击上传图片</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>标题/说明</label>
|
||
<input type="text" name="promo_image_1_title"
|
||
value="{{ config.promo_image_1_title or '' }}"
|
||
placeholder="扫码关注公众号">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>点击链接</label>
|
||
<input type="url" name="promo_image_1_link"
|
||
value="{{ config.promo_image_1_link or '' }}"
|
||
placeholder="https://example.com(可选)">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 宣传图片2 -->
|
||
<div class="promo-image-item">
|
||
<h4>图片 2</h4>
|
||
<div class="form-group">
|
||
<label>图片</label>
|
||
<div class="image-upload-box" onclick="document.getElementById('promo_image_2_file').click()">
|
||
<input type="file" id="promo_image_2_file" name="promo_image_2_file" accept="image/*" onchange="previewUpload(this, 'preview2', 'promo_image_2')">
|
||
<input type="hidden" id="promo_image_2" name="promo_image_2" value="{{ config.promo_image_2 or '' }}">
|
||
{% if config.promo_image_2 %}
|
||
<div id="preview2" class="upload-preview-has-image">
|
||
<img src="{{ config.promo_image_2 }}" alt="图片2">
|
||
<button type="button" class="remove-btn" onclick="removeImage('preview2', 'promo_image_2')">×</button>
|
||
</div>
|
||
{% else %}
|
||
<div id="preview2" class="upload-preview-empty">
|
||
<span>点击上传图片</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>标题/说明</label>
|
||
<input type="text" name="promo_image_2_title"
|
||
value="{{ config.promo_image_2_title or '' }}"
|
||
placeholder="加入技术社群">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>点击链接</label>
|
||
<input type="url" name="promo_image_2_link"
|
||
value="{{ config.promo_image_2_link or '' }}"
|
||
placeholder="https://example.com(可选)">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 宣传图片3(二维码) -->
|
||
<div class="promo-image-item">
|
||
<h4>图片 3(二维码)</h4>
|
||
<div class="form-group">
|
||
<label>图片</label>
|
||
<div class="image-upload-box" onclick="document.getElementById('promo_image_3_file').click()">
|
||
<input type="file" id="promo_image_3_file" name="promo_image_3_file" accept="image/*" onchange="previewUpload(this, 'preview3', 'promo_image_3')">
|
||
<input type="hidden" id="promo_image_3" name="promo_image_3" value="{{ config.promo_image_3 or '' }}">
|
||
{% if config.promo_image_3 %}
|
||
<div id="preview3" class="upload-preview-has-image">
|
||
<img src="{{ config.promo_image_3 }}" alt="图片3">
|
||
<button type="button" class="remove-btn" onclick="removeImage('preview3', 'promo_image_3')">×</button>
|
||
</div>
|
||
{% else %}
|
||
<div id="preview3" class="upload-preview-empty">
|
||
<span>点击上传二维码</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>标题/说明</label>
|
||
<input type="text" name="promo_image_3_title"
|
||
value="{{ config.promo_image_3_title or '' }}"
|
||
placeholder="微信扫码关注">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h3>联系方式</h3>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="contact_email">联系邮箱</label>
|
||
<input type="email" id="contact_email" name="contact_email"
|
||
value="{{ config.contact_email or '' }}"
|
||
placeholder="contact@example.com">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="contact_github">GitHub链接</label>
|
||
<input type="url" id="contact_github" name="contact_github"
|
||
value="{{ config.contact_github or '' }}"
|
||
placeholder="https://github.com/yourname">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<h3>显示设置</h3>
|
||
|
||
<div class="form-group">
|
||
<label for="articles_per_page">首页文章列表每页显示数量</label>
|
||
<input type="number" id="articles_per_page" name="articles_per_page"
|
||
value="{{ config.articles_per_page }}" min="5" max="50">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">保存设置</button>
|
||
</div>
|
||
</form>
|
||
|
||
<script>
|
||
// 图片上传预览
|
||
function previewUpload(input, previewId, hiddenId) {
|
||
const file = input.files[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const preview = document.getElementById(previewId);
|
||
preview.className = 'upload-preview-has-image';
|
||
preview.innerHTML = `
|
||
<img src="${e.target.result}" alt="预览">
|
||
<button type="button" class="remove-btn" onclick="removeImage('${previewId}', '${hiddenId}')">×</button>
|
||
`;
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
}
|
||
|
||
// Logo上传处理(异步上传)
|
||
function handleUpload(input, urlInputId) {
|
||
const file = input.files[0];
|
||
if (file) {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
fetch('{{ url_for("admin.upload_image") }}', {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
document.getElementById(urlInputId).value = data.url;
|
||
// 显示预览
|
||
const preview = input.closest('.form-group').querySelector('.image-preview');
|
||
if (preview) {
|
||
preview.innerHTML = `<img src="${data.url}" alt="预览" style="max-height: 50px;">`;
|
||
} else {
|
||
const newPreview = document.createElement('div');
|
||
newPreview.className = 'image-preview';
|
||
newPreview.innerHTML = `<img src="${data.url}" alt="预览" style="max-height: 50px;">`;
|
||
input.closest('.form-group').appendChild(newPreview);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 移除图片
|
||
function removeImage(previewId, hiddenId) {
|
||
const preview = document.getElementById(previewId);
|
||
preview.className = 'upload-preview-empty';
|
||
const placeholder = previewId === 'preview3' ? '点击上传二维码' : '点击上传图片';
|
||
preview.innerHTML = `<span>${placeholder}</span>`;
|
||
document.getElementById(hiddenId).value = '';
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.settings-form {
|
||
background: var(--admin-card-bg);
|
||
border-radius: 8px;
|
||
padding: 30px;
|
||
box-shadow: var(--admin-shadow);
|
||
}
|
||
|
||
.settings-section {
|
||
margin-bottom: 30px;
|
||
padding-bottom: 25px;
|
||
border-bottom: 1px solid var(--admin-border);
|
||
}
|
||
|
||
.settings-section:last-of-type {
|
||
border-bottom: none;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.settings-section h3 {
|
||
margin-bottom: 20px;
|
||
color: var(--admin-primary);
|
||
}
|
||
|
||
.form-hint {
|
||
display: block;
|
||
margin-bottom: 15px;
|
||
color: var(--admin-text-light);
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.image-upload-wrapper {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.image-upload-wrapper input[type="url"] {
|
||
flex: 1;
|
||
}
|
||
|
||
.promo-images-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
}
|
||
|
||
.promo-image-item {
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.promo-image-item h4 {
|
||
margin-bottom: 15px;
|
||
color: var(--admin-text);
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.promo-image-item .form-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.promo-image-item .form-group label {
|
||
font-size: 0.9rem;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.promo-image-item .form-group input {
|
||
font-size: 0.9rem;
|
||
padding: 8px 10px;
|
||
}
|
||
|
||
.image-upload-box {
|
||
border: 2px dashed var(--admin-border);
|
||
border-radius: 8px;
|
||
min-height: 120px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
}
|
||
|
||
.image-upload-box:hover {
|
||
border-color: var(--admin-primary);
|
||
}
|
||
|
||
.image-upload-box input[type="file"] {
|
||
display: none;
|
||
}
|
||
|
||
.upload-preview-empty {
|
||
color: var(--admin-text-light);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.upload-preview-has-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
}
|
||
|
||
.upload-preview-has-image img {
|
||
max-width: 100%;
|
||
max-height: 120px;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.remove-btn {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 50%;
|
||
background: #dc3545;
|
||
color: #fff;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.image-preview {
|
||
margin-top: 10px;
|
||
text-align: center;
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.promo-images-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %} |