初始化技术博客项目

功能特性:
- 前端文章展示 (Markdown渲染、代码高亮)
- 后台管理系统 (文章、分类、标签、作者管理)
- 图片上传 (点击、拖拽、粘贴)
- 网站设置 (Logo、底部宣传图片上传)
- 访问统计 (IP记录、趋势图表)
- 参考来源、附件支持
- 单端口部署 (16012)

技术栈:
- Flask 3.0 + SQLAlchemy
- SQLite数据库
- marked.js + highlight.js (Markdown渲染)
- Chart.js (统计图表)
This commit is contained in:
2026-06-04 23:12:34 +08:00
commit e689fed71e
53 changed files with 6506 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block title %}404 - 页面未找到{% endblock %}
{% block body %}
<div class="error-page">
<div class="container">
<h1>404</h1>
<p>页面未找到</p>
<a href="{{ url_for('frontend.index') }}" class="btn">返回首页</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block title %}500 - 服务器错误{% endblock %}
{% block body %}
<div class="error-page">
<div class="container">
<h1>500</h1>
<p>服务器内部错误</p>
<a href="{{ url_for('frontend.index') }}" class="btn">返回首页</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,157 @@
{% extends 'frontend/base.html' %}
{% block title %}{{ article.title }} - {{ g.site_config.site_name }}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/markdown.css') }}">
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/python.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/bash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/sql.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/json.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/yaml.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/dockerfile.min.js"></script>
<script>
// 配置 marked
marked.setOptions({
highlight: function(code, lang) {
if (lang && hlse.getLanguage(lang)) {
try {
return hlse.highlight(code, { language: lang }).value;
} catch (e) {}
}
return hlse.highlightAuto(code).value;
},
breaks: true,
gfm: true
});
// 渲染 Markdown
const content = document.getElementById('markdown-content');
if (content) {
const markdownText = content.getAttribute('data-content');
content.innerHTML = marked.parse(markdownText);
}
</script>
{% endblock %}
{% block content %}
<article class="article-detail">
<header class="article-header">
<h1 class="article-title">{{ article.title }}</h1>
<div class="article-meta">
{% if article.author %}
<span class="author">
{% if article.author.avatar %}
<img src="{{ article.author.avatar }}" alt="{{ article.author.name }}" class="author-avatar">
{% endif %}
{{ article.author.name }}
</span>
{% endif %}
{% if article.category %}
<span class="category">
<a href="{{ url_for('frontend.category', category_id=article.category.id) }}">
{{ article.category.name }}
</a>
</span>
{% endif %}
<span class="date">{{ article.published_at|datetime_format('%Y-%m-%d %H:%M') }}</span>
<span class="views">{{ article.view_count }} 阅读</span>
</div>
{% if article.tags %}
<div class="article-tags">
{% for tag in article.tags %}
<a href="{{ url_for('frontend.tag', tag_id=tag.id) }}"
class="tag" style="--tag-color: {{ tag.color }}">
{{ tag.name }}
</a>
{% endfor %}
</div>
{% endif %}
</header>
{% if article.summary %}
<div class="article-summary">
<strong>摘要:</strong>{{ article.summary }}
</div>
{% endif %}
{% if article.cover_image %}
<div class="article-cover">
<img src="{{ article.cover_image }}" alt="{{ article.title }}">
</div>
{% endif %}
<div id="markdown-content" class="article-content markdown-body" data-content="{{ article.content | e }}">
<!-- Markdown 将在这里渲染 -->
</div>
{% if article.author and article.author.bio %}
<div class="author-card">
{% if article.author.avatar %}
<img src="{{ article.author.avatar }}" alt="{{ article.author.name }}" class="author-avatar-lg">
{% endif %}
<div class="author-info">
<h4>{{ article.author.name }}</h4>
<p>{{ article.author.bio }}</p>
{% if article.author.website %}
<a href="{{ article.author.website }}" target="_blank">个人主页</a>
{% endif %}
</div>
</div>
{% endif %}
{% if article.references.all() %}
<div class="references-section">
<h3>📚 参考来源</h3>
<ul>
{% for ref in article.references %}
<li>
{% if ref.url %}
<a href="{{ ref.url }}" target="_blank">{{ ref.title }}</a>
{% else %}
{{ ref.title }}
{% endif %}
{% if ref.description %}
<span class="ref-desc">{{ ref.description }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if article.attachments.all() %}
<div class="attachments-section">
<h3>📎 相关附件</h3>
<ul>
{% for att in article.attachments %}
<li>
<a href="/uploads/{{ att.filename }}" target="blank">
{{ att.filename }}
</a>
<span class="filesize">{{ att.get_filesize_display() }}</span>
{% if att.description %}
<span class="att-desc">{{ att.description }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
<footer class="article-footer">
<div class="article-nav">
<a href="{{ url_for('frontend.index') }}">← 返回首页</a>
</div>
</footer>
</article>
{% endblock %}

View File

@@ -0,0 +1,163 @@
{% extends 'base.html' %}
{% block body %}
<nav class="navbar">
<div class="container">
<a href="{{ url_for('frontend.index') }}" class="logo">
{% if g.site_config.site_logo %}
<img src="{{ g.site_config.site_logo }}" alt="{{ g.site_config.site_name }}">
{% else %}
{{ g.site_config.site_name }}
{% endif %}
</a>
{% if g.site_config.site_subtitle %}
<span class="site-subtitle">{{ g.site_config.site_subtitle }}</span>
{% endif %}
<div class="nav-links">
<a href="{{ url_for('frontend.index') }}">首页</a>
{% for cat in categories %}
<a href="{{ url_for('frontend.category', category_id=cat.id) }}"
class="{% if current_category == cat.id %}active{% endif %}">{{ cat.name }}</a>
{% endfor %}
</div>
</div>
</nav>
<main class="main-content">
<div class="container">
<div class="content-wrapper">
<div class="content-area">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<aside class="sidebar">
<!-- 搜索 -->
<div class="sidebar-section">
<h3>搜索</h3>
<form class="search-form">
<input type="text" placeholder="搜索文章..." name="q">
<button type="submit">🔍</button>
</form>
</div>
<!-- 最新文章 -->
<div class="sidebar-section">
<h3>最新文章</h3>
<ul class="recent-list">
{% for article in recent_articles %}
<li>
<a href="{{ url_for('frontend.article', slug=article.slug) }}">
{{ article.title }}
</a>
</li>
{% endfor %}
</ul>
</div>
<!-- 热门文章 -->
<div class="sidebar-section">
<h3>热门文章</h3>
<ul class="hot-list">
{% for article in hot_articles %}
<li>
<a href="{{ url_for('frontend.article', slug=article.slug) }}">
<span class="rank">{{ loop.index }}</span>
{{ article.title }}
</a>
</li>
{% endfor %}
</ul>
</div>
<!-- 标签云 -->
<div class="sidebar-section">
<h3>标签云</h3>
<div class="tag-cloud">
{% for tag in tags %}
<a href="{{ url_for('frontend.tag', tag_id=tag.id) }}"
class="tag {% if current_tag == tag.id %}active{% endif %}"
style="--tag-color: {{ tag.color }}">
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
</aside>
</div>
</div>
</main>
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-main">
<p>{{ g.site_config.footer_text }}</p>
{% if g.site_config.footer_copyright %}
<p class="copyright">{{ g.site_config.footer_copyright }}</p>
{% endif %}
{% if g.site_config.footer_technology %}
<p class="technology">{{ g.site_config.footer_technology }}</p>
{% endif %}
{% if g.site_config.contact_email or g.site_config.contact_github %}
<div class="contact-links">
{% if g.site_config.contact_email %}
<a href="mailto:{{ g.site_config.contact_email }}">📧 {{ g.site_config.contact_email }}</a>
{% endif %}
{% if g.site_config.contact_github %}
<a href="{{ g.site_config.contact_github }}" target="_blank">🔗 GitHub</a>
{% endif %}
</div>
{% endif %}
</div>
{% if g.site_config.promo_image_1 or g.site_config.promo_image_2 or g.site_config.promo_image_3 %}
<div class="footer-promo">
{% if g.site_config.promo_image_1 %}
<div class="promo-item">
{% if g.site_config.promo_image_1_link %}
<a href="{{ g.site_config.promo_image_1_link }}" target="_blank" class="promo-link">
<img src="{{ g.site_config.promo_image_1 }}" alt="">
</a>
{% else %}
<div class="promo-link">
<img src="{{ g.site_config.promo_image_1 }}" alt="">
</div>
{% endif %}
</div>
{% endif %}
{% if g.site_config.promo_image_2 %}
<div class="promo-item">
{% if g.site_config.promo_image_2_link %}
<a href="{{ g.site_config.promo_image_2_link }}" target="_blank" class="promo-link">
<img src="{{ g.site_config.promo_image_2 }}" alt="">
</a>
{% else %}
<div class="promo-link">
<img src="{{ g.site_config.promo_image_2 }}" alt="">
</div>
{% endif %}
</div>
{% endif %}
{% if g.site_config.promo_image_3 %}
<div class="promo-item">
<div class="promo-link">
<img src="{{ g.site_config.promo_image_3 }}" alt="">
</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</footer>
{% endblock %}

View File

@@ -0,0 +1,97 @@
{% extends 'frontend/base.html' %}
{% block title %}
{% if category_title %}{{ category_title }} - 技术博客
{% elif tag_title %}{{ tag_title }} - 技术博客
{% else %}首页 - 技术博客{% endif %}
{% endblock %}
{% block content %}
<div class="page-header">
<h1>
{% if category_title %}
{{ category_title }}
{% elif tag_title %}
标签: {{ tag_title }}
{% else %}
技术文章
{% endif %}
</h1>
</div>
{% if articles.items %}
<div class="article-list">
{% for article in articles.items %}
<article class="article-card">
{% if article.cover_image %}
<div class="article-cover">
<img src="{{ article.cover_image }}" alt="{{ article.title }}">
</div>
{% endif %}
<div class="article-body">
<h2 class="article-title">
<a href="{{ url_for('frontend.article', slug=article.slug) }}">
{{ article.title }}
</a>
</h2>
<div class="article-meta">
{% if article.category %}
<span class="category">{{ article.category.name }}</span>
{% endif %}
<span class="date">{{ article.published_at|datetime_format('%Y-%m-%d') }}</span>
<span class="views">{{ article.view_count }} 阅读</span>
</div>
<p class="article-summary">{{ article.summary|truncate_content(200) }}</p>
{% if article.tags %}
<div class="article-tags">
{% for tag in article.tags %}
<a href="{{ url_for('frontend.tag', tag_id=tag.id) }}"
class="tag" style="--tag-color: {{ tag.color }}">
{{ tag.name }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
</article>
{% endfor %}
</div>
<!-- 分页 -->
{% if articles.pages > 1 %}
<div class="pagination">
{% if articles.has_prev %}
<a href="{{ url_for('frontend.index', page=articles.prev_num, category=current_category, tag=current_tag) }}"
class="page-link">上一页</a>
{% endif %}
{% for page in articles.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
{% if page %}
{% if page == articles.page %}
<span class="page-link active">{{ page }}</span>
{% else %}
<a href="{{ url_for('frontend.index', page=page, category=current_category, tag=current_tag) }}"
class="page-link">{{ page }}</a>
{% endif %}
{% else %}
<span class="page-link ellipsis">...</span>
{% endif %}
{% endfor %}
{% if articles.has_next %}
<a href="{{ url_for('frontend.index', page=articles.next_num, category=current_category, tag=current_tag) }}"
class="page-link">下一页</a>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="empty-state">
<p>暂无文章</p>
</div>
{% endif %}
{% endblock %}