AI 生成: 一个简洁优雅的个人博客网站,包含首页文章列表、文章详情页和关于页面。使用 Python Flask 构建,支持 Markdown 文章渲染。

This commit is contained in:
admin
2026-09-28 16:47:07 +08:00
commit 53852dbbb0
11 changed files with 678 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# Personal Blog
一个简洁优雅的个人博客网站。
## 功能特性
- 首页文章列表
- 文章详情页(支持 Markdown 渲染)
- 关于页面
- 响应式设计,简洁优雅的界面
## 技术栈
- Python 3.8+
- Flask
- Markdown
- HTML5 / CSS3
## 目录结构
```
personal-blog/
├── app.py # Flask 应用入口
├── requirements.txt # 依赖文件
├── README.md # 说明文件
├── posts/ # Markdown 文章目录
│ ├── hello-world.md
│ └── python-tips.md
├── templates/ # HTML 模板
│ ├── base.html
│ ├── index.html
│ ├── post.html
│ └── about.html
└── static/
└── css/
└── style.css
```
## 快速开始
1. 安装依赖:
```bash
pip install -r requirements.txt
```
2. 启动应用:
```bash
python app.py
```
3. 打开浏览器访问:
```
http://127.0.0.1:5000
```
## 添加新文章
在 `posts/` 目录下创建新的 `.md` 文件,文件名即为 URL 路径。
文件头部使用 YAML front matter 定义元信息:
```markdown
---
title: 我的文章标题
date: 2024-01-01
summary: 文章摘要
---
正文内容...
```
## License
MIT
+95
View File
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
"""
个人博客 - Flask 应用入口
"""
import os
import re
from datetime import datetime
from flask import Flask, render_template, abort
import markdown
app = Flask(__name__)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
POSTS_DIR = os.path.join(BASE_DIR, 'posts')
def parse_front_matter(text):
"""解析 Markdown 文件的 YAML front matter(简单实现)"""
meta = {}
body = text
match = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)$', text, re.DOTALL)
if match:
raw_meta, body = match.group(1), match.group(2)
for line in raw_meta.strip().splitlines():
if ':' in line:
key, value = line.split(':', 1)
meta[key.strip()] = value.strip()
return meta, body
def load_posts():
"""加载所有文章,返回按日期倒序的列表"""
posts = []
if not os.path.isdir(POSTS_DIR):
return posts
for filename in os.listdir(POSTS_DIR):
if not filename.endswith('.md'):
continue
slug = filename[:-3]
path = os.path.join(POSTS_DIR, filename)
with open(path, 'r', encoding='utf-8') as f:
raw = f.read()
meta, body = parse_front_matter(raw)
posts.append({
'slug': slug,
'title': meta.get('title', slug),
'date': meta.get('date', ''),
'summary': meta.get('summary', ''),
'content': body,
})
posts.sort(key=lambda p: p['date'], reverse=True)
return posts
def get_post(slug):
"""根据 slug 获取单篇文章"""
for post in load_posts():
if post['slug'] == slug:
return post
return None
@app.route('/')
def index():
"""首页:文章列表"""
posts = load_posts()
return render_template('index.html', posts=posts)
@app.route('/post/<slug>')
def post_detail(slug):
"""文章详情页"""
post = get_post(slug)
if not post:
abort(404)
html_content = markdown.markdown(
post['content'],
extensions=['fenced_code', 'codehilite', 'tables']
)
return render_template('post.html', post=post, content=html_content)
@app.route('/about')
def about():
"""关于页面"""
return render_template('about.html')
@app.errorhandler(404)
def not_found(e):
return render_template('404.html'), 404
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=5000)
+33
View File
@@ -0,0 +1,33 @@
---
title: 你好,世界
date: 2024-01-15
summary: 这是博客的第一篇文章,记录下开始写作的这一刻。
---
## 为什么写博客
写作是最好的思考方式。当你尝试把想法用文字表达出来时,会发现自己对问题的理解是否真正清晰。
这个博客会记录我在:
- 编程与技术上的学习
- 阅读与思考的收获
- 生活里的小确幸
## 关于技术
本站使用 **Python Flask** 构建,文章用 Markdown 编写。
```python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, World!'
```
> 保持简单,保持专注。
希望你能在这里找到一些有价值的东西。
+48
View File
@@ -0,0 +1,48 @@
---
title: 5 个实用的 Python 小技巧
date: 2024-02-20
summary: 分享几个日常开发中非常实用的 Python 技巧,让你的代码更简洁。
---
## 1. 使用 enumerate 获取索引
```python
fruits = ['apple', 'banana', 'cherry']
for i, fruit in enumerate(fruits):
print(i, fruit)
```
## 2. 列表推导式
用一行代码完成过滤和映射:
```python
squares = [x * x for x in range(10) if x % 2 == 0]
```
## 3. 使用 zip 同时遍历
```python
names = ['Alice', 'Bob']
ages = [25, 30]
for name, age in zip(names, ages):
print(f'{name} is {age}')
```
## 4. 字典的 get 方法
避免 KeyError:
```python
config = {'debug': True}
level = config.get('level', 'INFO')
```
## 5. 使用 f-string 格式化
```python
name = 'World'
print(f'Hello, {name}!')
```
这些小技巧虽然简单,但能让代码更 Pythonic。
+2
View File
@@ -0,0 +1,2 @@
Flask==3.0.0
Markdown==3.5.1
+313
View File
@@ -0,0 +1,313 @@
/* ---------- 基础样式 ---------- */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
color: #333;
background: #fafafa;
line-height: 1.75;
font-size: 16px;
}
a {
color: #2b6cb0;
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: #1a4d80;
}
.container {
max-width: 720px;
margin: 0 auto;
padding: 0 24px;
}
/* ---------- 头部 ---------- */
.site-header {
background: #fff;
border-bottom: 1px solid #eee;
padding: 20px 0;
margin-bottom: 48px;
}
.header-inner {
display: flex;
justify-content: space-between;
align-items: center;
}
.site-title {
font-size: 20px;
font-weight: 600;
color: #222;
letter-spacing: 0.5px;
}
.site-title:hover {
color: #2b6cb0;
}
.site-nav a {
margin-left: 20px;
color: #666;
font-size: 15px;
}
.site-nav a:hover {
color: #2b6cb0;
}
/* ---------- 首页 Hero ---------- */
.hero {
text-align: center;
margin-bottom: 56px;
}
.hero h1 {
font-size: 32px;
font-weight: 600;
color: #222;
margin-bottom: 12px;
}
.hero .subtitle {
color: #888;
font-size: 16px;
}
/* ---------- 文章列表 ---------- */
.post-list {
display: flex;
flex-direction: column;
gap: 40px;
}
.post-item {
padding-bottom: 32px;
border-bottom: 1px solid #eee;
}
.post-item:last-child {
border-bottom: none;
}
.post-title {
font-size: 22px;
font-weight: 600;
margin-bottom: 8px;
}
.post-title a {
color: #222;
}
.post-title a:hover {
color: #2b6cb0;
}
.post-meta {
color: #999;
font-size: 14px;
margin-bottom: 12px;
}
.post-summary {
color: #555;
margin-bottom: 12px;
}
.read-more {
font-size: 14px;
color: #2b6cb0;
}
.empty-tip {
color: #999;
text-align: center;
padding: 40px 0;
}
/* ---------- 文章详情 ---------- */
.post-detail {
background: #fff;
padding: 40px 36px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.post-header {
margin-bottom: 32px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.post-header h1 {
font-size: 28px;
font-weight: 600;
color: #222;
margin-bottom: 8px;
line-height: 1.4;
}
.post-content h2 {
font-size: 20px;
margin: 32px 0 16px;
color: #222;
}
.post-content h3 {
font-size: 17px;
margin: 24px 0 12px;
color: #222;
}
.post-content p {
margin-bottom: 16px;
}
.post-content ul,
.post-content ol {
margin: 0 0 16px 24px;
}
.post-content li {
margin-bottom: 6px;
}
.post-content blockquote {
border-left: 3px solid #ddd;
padding: 8px 16px;
color: #666;
background: #f9f9f9;
margin: 16px 0;
border-radius: 0 4px 4px 0;
}
.post-content code {
background: #f4f4f4;
padding: 2px 6px;
border-radius: 4px;
font-size: 14px;
font-family: "SFMono-Regular", Consolas, Menlo, monospace;
}
.post-content pre {
background: #2d2d2d;
color: #f8f8f2;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
margin: 16px 0;
}
.post-content pre code {
background: transparent;
padding: 0;
color: inherit;
}
.post-footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.back-link {
color: #666;
font-size: 14px;
}
.back-link:hover {
color: #2b6cb0;
}
/* ---------- 关于页面 ---------- */
.about {
background: #fff;
padding: 40px 36px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.about h1 {
font-size: 26px;
margin-bottom: 20px;
color: #222;
}
.about h2 {
font-size: 18px;
margin: 28px 0 12px;
color: #222;
}
.about p {
margin-bottom: 14px;
color: #444;
}
.contact-list {
list-style: none;
padding: 0;
}
.contact-list li {
margin-bottom: 8px;
color: #555;
}
/* ---------- 404 ---------- */
.not-found {
text-align: center;
padding: 80px 0;
}
.not-found h1 {
font-size: 72px;
color: #ddd;
margin-bottom: 16px;
}
.not-found p {
color: #888;
margin-bottom: 24px;
}
/* ---------- 底部 ---------- */
.site-footer {
margin-top: 80px;
padding: 32px 0;
text-align: center;
color: #aaa;
font-size: 14px;
border-top: 1px solid #eee;
}
/* ---------- 响应式 ---------- */
@media (max-width: 640px) {
.hero h1 {
font-size: 26px;
}
.post-detail,
.about {
padding: 28px 20px;
}
.post-header h1 {
font-size: 22px;
}
.header-inner {
flex-direction: column;
gap: 12px;
}
.site-nav a {
margin: 0 10px;
}
}
+10
View File
@@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block title %}页面未找到 - 我的博客{% endblock %}
{% block content %}
<section class="not-found">
<h1>404</h1>
<p>抱歉,你访问的页面不存在。</p>
<a href="{{ url_for('index') }}" class="back-link">← 返回首页</a>
</section>
{% endblock %}
+19
View File
@@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block title %}关于 - 我的博客{% endblock %}
{% block content %}
<section class="about">
<h1>关于我</h1>
<p>你好,欢迎来到我的个人博客!</p>
<p>这里记录我在编程、生活、阅读中的思考与成长。我相信写作是最好的思考方式,也希望这些文字能对你有所启发。</p>
<h2>联系方式</h2>
<ul class="contact-list">
<li>邮箱:hello@example.com</li>
<li>GitHub:<a href="https://github.com/" target="_blank" rel="noopener">github.com</a></li>
</ul>
<h2>关于本站</h2>
<p>本站使用 Python Flask 构建,文章以 Markdown 格式编写,简洁、纯粹。</p>
</section>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的博客{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a href="{{ url_for('index') }}" class="site-title">我的博客</a>
<nav class="site-nav">
<a href="{{ url_for('index') }}">首页</a>
<a href="{{ url_for('about') }}">关于</a>
</nav>
</div>
</header>
<main class="container">
{% block content %}{% endblock %}
</main>
<footer class="site-footer">
<div class="container">
<p>&copy; 2024 我的博客 · 用心记录每一刻</p>
</div>
</footer>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% block title %}首页 - 我的博客{% endblock %}
{% block content %}
<section class="hero">
<h1>欢迎来到我的博客</h1>
<p class="subtitle">记录思考,分享成长</p>
</section>
<section class="post-list">
{% if posts %}
{% for post in posts %}
<article class="post-item">
<h2 class="post-title">
<a href="{{ url_for('post_detail', slug=post.slug) }}">{{ post.title }}</a>
</h2>
<div class="post-meta">
<time>{{ post.date }}</time>
</div>
{% if post.summary %}
<p class="post-summary">{{ post.summary }}</p>
{% endif %}
<a class="read-more" href="{{ url_for('post_detail', slug=post.slug) }}">阅读全文 →</a>
</article>
{% endfor %}
{% else %}
<p class="empty-tip">还没有文章,去 <code>posts/</code> 目录添加吧。</p>
{% endif %}
</section>
{% endblock %}
+21
View File
@@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% block title %}{{ post.title }} - 我的博客{% endblock %}
{% block content %}
<article class="post-detail">
<header class="post-header">
<h1>{{ post.title }}</h1>
<div class="post-meta">
<time>{{ post.date }}</time>
</div>
</header>
<div class="post-content">
{{ content|safe }}
</div>
<div class="post-footer">
<a href="{{ url_for('index') }}" class="back-link">← 返回首页</a>
</div>
</article>
{% endblock %}