Python编程教程之三:实例

十四、实际应用项目案例

在这一部分,我们将通过几个实际的项目案例来展示Python在不同领域的应用。这些案例将帮助你理解如何将前面学到的知识应用到实际项目中。

14.1 数据分析项目:学生成绩分析

在这个项目中,我们将使用Python来分析学生成绩数据,包括数据清洗、数据分析和数据可视化。

复制代码
# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
sns.set(style='whitegrid', font='WenQuanYi Zen Hei', rc={'axes.unicode_minus': False})

# 1. 数据准备
# 创建模拟数据
np.random.seed(42)
num_students = 100

# 生成学生数据
students = []
for i in range(1, num_students + 1):
    student_id = f"S{i:03d}"
    name = f"学生{i}"
    gender = np.random.choice(['男', '女'])
    age = np.random.randint(18, 22)
    major = np.random.choice(['计算机科学', '数学', '物理', '化学', '生物'])
    
    # 生成成绩数据
    math = np.random.normal(75, 10)
    physics = np.random.normal(70, 12)
    chemistry = np.random.normal(72, 11)
    biology = np.random.normal(68, 13)
    english = np.random.normal(80, 8)
    
    # 确保成绩在0-100之间
    math = max(0, min(100, math))
    physics = max(0, min(100, physics))
    chemistry = max(0, min(100, chemistry))
    biology = max(0, min(100, biology))
    english = max(0, min(100, english))
    
    # 计算总分和平均分
    total_score = math + physics + chemistry + biology + english
    average_score = total_score / 5
    
    # 确定等级
    if average_score >= 90:
        grade = 'A'
    elif average_score >= 80:
        grade = 'B'
    elif average_score >= 70:
        grade = 'C'
    elif average_score >= 60:
        grade = 'D'
    else:
        grade = 'F'
    
    students.append({
        '学号': student_id,
        '姓名': name,
        '性别': gender,
        '年龄': age,
        '专业': major,
        '数学': math,
        '物理': physics,
        '化学': chemistry,
        '生物': biology,
        '英语': english,
        '总分': total_score,
        '平均分': average_score,
        '等级': grade
    })

# 创建DataFrame
df = pd.DataFrame(students)

# 保存数据到CSV文件
df.to_csv('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/student_scores.csv', index=False, encoding='utf-8')

# 2. 数据探索
# 显示数据的前几行
print("数据前5行:")
print(df.head())

# 显示数据的基本信息
print("\n数据基本信息:")
print(df.info())

# 显示数据的统计描述
print("\n数据统计描述:")
print(df.describe())

# 3. 数据清洗
# 检查缺失值
print("\n缺失值检查:")
print(df.isnull().sum())

# 检查重复值
print("\n重复值检查:")
print(df.duplicated().sum())

# 4. 数据分析
# 按性别分析
print("\n按性别分析:")
gender_stats = df.groupby('性别').agg({
    '数学': ['mean', 'std'],
    '物理': ['mean', 'std'],
    '化学': ['mean', 'std'],
    '生物': ['mean', 'std'],
    '英语': ['mean', 'std'],
    '平均分': ['mean', 'std']
})
print(gender_stats)

# 按专业分析
print("\n按专业分析:")
major_stats = df.groupby('专业').agg({
    '数学': ['mean', 'std'],
    '物理': ['mean', 'std'],
    '化学': ['mean', 'std'],
    '生物': ['mean', 'std'],
    '英语': ['mean', 'std'],
    '平均分': ['mean', 'std']
})
print(major_stats)

# 按等级分析
print("\n按等级分析:")
grade_counts = df['等级'].value_counts()
print(grade_counts)

# 5. 数据可视化
# 按性别和专业的平均分比较
plt.figure(figsize=(12, 6))
sns.barplot(x='专业', y='平均分', hue='性别', data=df)
plt.title('不同专业和性别的平均分比较')
plt.savefig('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/gender_major_avg_score.png')
plt.close()

# 各科成绩的分布
plt.figure(figsize=(12, 6))
subjects = ['数学', '物理', '化学', '生物', '英语']
for i, subject in enumerate(subjects):
    plt.subplot(2, 3, i+1)
    sns.histplot(df[subject], kde=True)
    plt.title(f'{subject}成绩分布')
plt.tight_layout()
plt.savefig('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/subject_score_distribution.png')
plt.close()

# 等级分布
plt.figure(figsize=(10, 6))
sns.countplot(x='等级', data=df, order=['A', 'B', 'C', 'D', 'F'])
plt.title('学生等级分布')
plt.savefig('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/grade_distribution.png')
plt.close()

# 各科成绩的相关性
plt.figure(figsize=(10, 8))
corr = df[['数学', '物理', '化学', '生物', '英语']].corr()
sns.heatmap(corr, annot=True, cmap='coolwarm')
plt.title('各科成绩相关性')
plt.savefig('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/subject_correlation.png')
plt.close()

# 6. 高级分析
# 标准化数据
scaler = StandardScaler()
scaled_data = scaler.fit_transform(df[['数学', '物理', '化学', '生物', '英语']])

# 聚类分析
kmeans = KMeans(n_clusters=3, random_state=42)
df['聚类'] = kmeans.fit_predict(scaled_data)

# 可视化聚类结果
plt.figure(figsize=(10, 6))
sns.scatterplot(x='数学', y='物理', hue='聚类', data=df, palette='viridis')
plt.title('学生聚类结果(基于数学和物理成绩)')
plt.savefig('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/student_clustering.png')
plt.close()

# 主成分分析
pca = PCA(n_components=2)
principal_components = pca.fit_transform(scaled_data)
df['PC1'] = principal_components[:, 0]
df['PC2'] = principal_components[:, 1]

# 可视化主成分分析结果
plt.figure(figsize=(10, 6))
sns.scatterplot(x='PC1', y='PC2', hue='等级', data=df, palette='viridis')
plt.title('学生成绩主成分分析')
plt.savefig('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/student_pca.png')
plt.close()

# 7. 结果解释
# 分析聚类结果
print("\n聚类结果分析:")
cluster_stats = df.groupby('聚类').agg({
    '数学': 'mean',
    '物理': 'mean',
    '化学': 'mean',
    '生物': 'mean',
    '英语': 'mean',
    '平均分': 'mean',
    '等级': lambda x: x.value_counts().index[0]  # 众数
})
print(cluster_stats)

# 分析主成分
print("\n主成分分析:")
print(f"第一主成分解释的方差比例: {pca.explained_variance_ratio_[0]:.2f}")
print(f"第二主成分解释的方差比例: {pca.explained_variance_ratio_[1]:.2f}")
print(f"两个主成分总共解释的方差比例: {sum(pca.explained_variance_ratio_):.2f}")

# 8. 生成报告
# 创建HTML报告
html_report = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>学生成绩分析报告</title>
    <style>
        body {
            font-family: 'WenQuanYi Zen Hei', Arial, sans-serif;
            line-height: 1.6;
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;
            color: #333;
        }
        h1, h2, h3 {
            color: #2c3e50;
        }
        h1 {
            border-bottom: 2px solid #3498db;
            padding-bottom: 10px;
        }
        table {
            border-collapse: collapse;
            width: 100%;
            margin: 20px 0;
        }
        table, th, td {
            border: 1px solid #ddd;
        }
        th, td {
            padding: 12px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        tr:nth-child(even) {
            background-color: #f9f9f9;
        }
        img {
            max-width: 100%;
            height: auto;
            margin: 20px 0;
            border: 1px solid #ddd;
        }
        .note {
            background-color: #fffbea;
            border-left: 4px solid #f1c40f;
            padding: 10px 15px;
            margin: 15px 0;
        }
        .tip {
            background-color: #e8f8f5;
            border-left: 4px solid #1abc9c;
            padding: 10px 15px;
            margin: 15px 0;
        }
    </style>
</head>
<body>
    <h1>学生成绩分析报告</h1>
    
    <h2>1. 数据概述</h2>
    <p>本报告分析了{}名学生的成绩数据,包括数学、物理、化学、生物和英语五门课程的成绩。</p>
    
    <h2>2. 数据统计</h2>
    <h3>2.1 基本统计信息</h3>
    <table>
        <tr>
            <th>统计量</th>
            <th>数学</th>
            <th>物理</th>
            <th>化学</th>
            <th>生物</th>
            <th>英语</th>
            <th>平均分</th>
        </tr>
        <tr>
            <td>平均分</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
        </tr>
        <tr>
            <td>标准差</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
        </tr>
        <tr>
            <td>最低分</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
        </tr>
        <tr>
            <td>最高分</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
            <td>{:.2f}</td>
        </tr>
    </table>
    
    <h3>2.2 等级分布</h3>
    <table>
        <tr>
            <th>等级</th>
            <th>人数</th>
            <th>百分比</th>
        </tr>
""".format(num_students,
          df['数学'].mean(), df['物理'].mean(), df['化学'].mean(), df['生物'].mean(), df['英语'].mean(), df['平均分'].mean(),
          df['数学'].std(), df['物理'].std(), df['化学'].std(), df['生物'].std(), df['英语'].std(), df['平均分'].std(),
          df['数学'].min(), df['物理'].min(), df['化学'].min(), df['生物'].min(), df['英语'].min(), df['平均分'].min(),
          df['数学'].max(), df['物理'].max(), df['化学'].max(), df['生物'].max(), df['英语'].max(), df['平均分'].max())

for grade in ['A', 'B', 'C', 'D', 'F']:
    count = grade_counts.get(grade, 0)
    percentage = count / num_students * 100
    html_report += f"""
        <tr>
            <td>{grade}</td>
            <td>{count}</td>
            <td>{percentage:.1f}%</td>
        </tr>
"""

html_report += """
    </table>
    
    <h2>3. 数据可视化</h2>
    <h3>3.1 不同专业和性别的平均分比较</h3>
    <img src="gender_major_avg_score.png" alt="不同专业和性别的平均分比较">
    
    <h3>3.2 各科成绩分布</h3>
    <img src="subject_score_distribution.png" alt="各科成绩分布">
    
    <h3>3.3 学生等级分布</h3>
    <img src="grade_distribution.png" alt="学生等级分布">
    
    <h3>3.4 各科成绩相关性</h3>
    <img src="subject_correlation.png" alt="各科成绩相关性">
    
    <h2>4. 高级分析</h2>
    <h3>4.1 学生聚类分析</h3>
    <img src="student_clustering.png" alt="学生聚类结果">
    <p>通过K均值聚类算法,我们将学生分为3个群体。从图中可以看出,不同群体的学生在数学和物理成绩上有明显差异。</p>
    
    <h3>4.2 主成分分析</h3>
    <img src="student_pca.png" alt="学生成绩主成分分析">
    <p>通过主成分分析,我们将五门课程的成绩降维到两个主成分,这两个主成分总共解释了{:.2f}%的方差。从图中可以看出,不同等级的学生在主成分空间中有一定的分离。</p>
    
    <h2>5. 结论与建议</h2>
    <h3>5.1 主要发现</h3>
    <ul>
        <li>英语成绩普遍较高,平均分为{:.2f}分。</li>
        <li>生物成绩相对较低,平均分为{:.2f}分。</li>
        <li>数学和物理成绩之间有较强的正相关性。</li>
        <li>不同专业的学生在各科成绩上存在差异。</li>
        <li>通过聚类分析,可以将学生分为3个群体,每个群体有不同的成绩特点。</li>
    </ul>
    
    <h3>5.2 建议</h3>
    <ul>
        <li>针对生物成绩较低的问题,可以考虑改进教学方法或增加学习资源。</li>
        <li>对于成绩较差的学生群体,可以提供额外的辅导和支持。</li>
        <li>利用数学和物理成绩之间的相关性,可以考虑整合这两门课程的教学内容。</li>
        <li>根据不同专业的学生成绩特点,可以调整各专业的课程设置和教学重点。</li>
    </ul>
    
    <div class="note">
        <p>注意:本报告基于模拟数据生成,仅用于演示数据分析流程和方法。</p>
    </div>
</body>
</html>
""".format(sum(pca.explained_variance_ratio_),
          df['英语'].mean(),
          df['生物'].mean())

# 保存HTML报告
with open('/home/wuying/autoglm/session_a2dc1bc4-2ea2-49d3-a5db-90c5e4232fd4/student_scores_report.html', 'w', encoding='utf-8') as f:
    f.write(html_report)

print("分析完成,报告已生成。")

14.2 Web应用项目:个人博客系统

在这个项目中,我们将使用Flask框架创建一个简单的个人博客系统,包括文章发布、用户认证和评论功能。

复制代码
# app.py
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
import os
from functools import wraps

# 创建Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 初始化数据库
db = SQLAlchemy(app)
migrate = Migrate(app, db)

# 定义数据库模型
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    posts = db.relationship('Post', backref='author', lazy=True)
    comments = db.relationship('Comment', backref='author', lazy=True)
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
    
    def __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    comments = db.relationship('Comment', backref='post', lazy=True, cascade='all, delete-orphan')
    
    def __repr__(self):
        return f'<Post {self.title}>'

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
    
    def __repr__(self):
        return f'<Comment {self.id}>'

# 登录装饰器
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('请先登录', 'danger')
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

# 路由和视图函数
@app.route('/')
def index():
    page = request.args.get('page', 1, type=int)
    posts = Post.query.order_by(Post.created_at.desc()).paginate(
        page=page, per_page=5, error_out=False)
    return render_template('index.html', posts=posts)

@app.route('/post/<int:post_id>')
def post_detail(post_id):
    post = Post.query.get_or_404(post_id)
    comments = Comment.query.filter_by(post_id=post.id).order_by(Comment.created_at.desc()).all()
    return render_template('post_detail.html', post=post, comments=comments)

@app.route('/create', methods=['GET', 'POST'])
@login_required
def create_post():
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']
        
        if not title or not content:
            flash('请填写标题和内容', 'danger')
            return render_template('create_post.html')
        
        post = Post(title=title, content=content, user_id=session['user_id'])
        db.session.add(post)
        db.session.commit()
        
        flash('文章创建成功', 'success')
        return redirect(url_for('post_detail', post_id=post.id))
    
    return render_template('create_post.html')

@app.route('/edit/<int:post_id>', methods=['GET', 'POST'])
@login_required
def edit_post(post_id):
    post = Post.query.get_or_404(post_id)
    
    if post.user_id != session['user_id']:
        flash('你没有权限编辑这篇文章', 'danger')
        return redirect(url_for('post_detail', post_id=post.id))
    
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']
        
        if not title or not content:
            flash('请填写标题和内容', 'danger')
            return render_template('edit_post.html', post=post)
        
        post.title = title
        post.content = content
        post.updated_at = datetime.utcnow()
        db.session.commit()
        
        flash('文章更新成功', 'success')
        return redirect(url_for('post_detail', post_id=post.id))
    
    return render_template('edit_post.html', post=post)

@app.route('/delete/<int:post_id>', methods=['POST'])
@login_required
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    
    if post.user_id != session['user_id']:
        flash('你没有权限删除这篇文章', 'danger')
        return redirect(url_for('post_detail', post_id=post.id))
    
    db.session.delete(post)
    db.session.commit()
    
    flash('文章删除成功', 'success')
    return redirect(url_for('index'))

@app.route('/comment/<int:post_id>', methods=['POST'])
@login_required
def add_comment(post_id):
    post = Post.query.get_or_404(post_id)
    content = request.form['content']
    
    if not content:
        flash('请填写评论内容', 'danger')
        return redirect(url_for('post_detail', post_id=post.id))
    
    comment = Comment(content=content, user_id=session['user_id'], post_id=post.id)
    db.session.add(comment)
    db.session.commit()
    
    flash('评论添加成功', 'success')
    return redirect(url_for('post_detail', post_id=post.id))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        email = request.form['email']
        password = request.form['password']
        confirm_password = request.form['confirm_password']
        
        if not username or not email or not password or not confirm_password:
            flash('请填写所有字段', 'danger')
            return render_template('register.html')
        
        if password != confirm_password:
            flash('两次输入的密码不一致', 'danger')
            return render_template('register.html')
        
        if User.query.filter_by(username=username).first():
            flash('用户名已存在', 'danger')
            return render_template('register.html')
        
        if User.query.filter_by(email=email).first():
            flash('邮箱已被注册', 'danger')
            return render_template('register.html')
        
        user = User(username=username, email=email)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()
        
        flash('注册成功,请登录', 'success')
        return redirect(url_for('login'))
    
    return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        
        if not username or not password:
            flash('请填写用户名和密码', 'danger')
            return render_template('login.html')
        
        user = User.query.filter_by(username=username).first()
        
        if user and user.check_password(password):
            session['user_id'] = user.id
            session['username'] = user.username
            flash('登录成功', 'success')
            return redirect(url_for('index'))
        else:
            flash('用户名或密码错误', 'danger')
    
    return render_template('login.html')

@app.route('/logout')
def logout():
    session.clear()
    flash('已退出登录', 'info')
    return redirect(url_for('index'))

@app.route('/profile')
@login_required
def profile():
    user = User.query.get(session['user_id'])
    posts = Post.query.filter_by(user_id=user.id).order_by(Post.created_at.desc()).all()
    return render_template('profile.html', user=user, posts=posts)

# API路由
@app.route('/api/posts')
def api_posts():
    posts = Post.query.order_by(Post.created_at.desc()).all()
    posts_list = []
    for post in posts:
        posts_list.append({
            'id': post.id,
            'title': post.title,
            'content': post.content,
            'author': post.author.username,
            'created_at': post.created_at.isoformat(),
            'updated_at': post.updated_at.isoformat()
        })
    return jsonify(posts_list)

@app.route('/api/post/<int:post_id>')
def api_post(post_id):
    post = Post.query.get_or_404(post_id)
    comments = Comment.query.filter_by(post_id=post.id).order_by(Comment.created_at.desc()).all()
    comments_list = []
    for comment in comments:
        comments_list.append({
            'id': comment.id,
            'content': comment.content,
            'author': comment.author.username,
            'created_at': comment.created_at.isoformat()
        })
    
    return jsonify({
        'id': post.id,
        'title': post.title,
        'content': post.content,
        'author': post.author.username,
        'created_at': post.created_at.isoformat(),
        'updated_at': post.updated_at.isoformat(),
        'comments': comments_list
    })

# 错误处理
@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500

# 创建数据库表
@app.before_first_request
def create_tables():
    db.create_all()

# 启动应用
if __name__ == '__main__':
    app.run(debug=True)

下面是博客系统的HTML模板文件:

复制代码
<!-- templates/base.html -->
<!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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            font-family: 'WenQuanYi Zen Hei', Arial, sans-serif;
            background-color: #f8f9fa;
        }
        .navbar-brand {
            font-weight: bold;
        }
        .card {
            margin-bottom: 20px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .card-title {
            color: #2c3e50;
        }
        .comment {
            border-left: 3px solid #3498db;
            padding-left: 15px;
            margin-bottom: 15px;
        }
        .comment-meta {
            font-size: 0.8rem;
            color: #6c757d;
        }
        footer {
            margin-top: 50px;
            padding: 20px 0;
            background-color: #343a40;
            color: white;
            text-align: center;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{{ url_for('index') }}">个人博客</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('index') }}">首页</a>
                    </li>
                </ul>
                <ul class="navbar-nav">
                    {% if session.user_id %}
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('create_post') }}">写文章</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('profile') }}">个人中心</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('logout') }}">退出登录</a>
                    </li>
                    {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('login') }}">登录</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('register') }}">注册</a>
                    </li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>

    <div class="container mt-4">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </div>

    <footer>
        <div class="container">
            <p>© 2023 个人博客. 保留所有权利.</p>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    {% block scripts %}{% endblock %}
</body>
</html>

<!-- templates/index.html -->
{% extends "base.html" %}

{% block title %}首页 - 个人博客{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8">
        <h2>最新文章</h2>
        
        {% for post in posts.items %}
        <div class="card">
            <div class="card-body">
                <h5 class="card-title"><a href="{{ url_for('post_detail', post_id=post.id) }}">{{ post.title }}</a></h5>
                <p class="card-text">{{ post.content[:200] }}...</p>
                <p class="card-text">
                    <small class="text-muted">
                        作者: {{ post.author.username }} | 
                        发布时间: {{ post.created_at.strftime('%Y-%m-%d %H:%M') }}
                        {% if post.updated_at != post.created_at %}
                        | 更新时间: {{ post.updated_at.strftime('%Y-%m-%d %H:%M') }}
                        {% endif %}
                    </small>
                </p>
            </div>
        </div>
        {% endfor %}
        
        <!-- 分页 -->
        {% if posts.pages > 1 %}
        <nav aria-label="Page navigation">
            <ul class="pagination justify-content-center">
                {% if posts.has_prev %}
                <li class="page-item">
                    <a class="page-link" href="{{ url_for('index', page=posts.prev_num) }}">上一页</a>
                </li>
                {% endif %}
                
                {% for page_num in posts.iter_pages() %}
                    {% if page_num %}
                        {% if page_num != posts.page %}
                        <li class="page-item">
                            <a class="page-link" href="{{ url_for('index', page=page_num) }}">{{ page_num }}</a>
                        </li>
                        {% else %}
                        <li class="page-item active">
                            <span class="page-link">{{ page_num }}</span>
                        </li>
                        {% endif %}
                    {% else %}
                    <li class="page-item disabled">
                        <span class="page-link">...</span>
                    </li>
                    {% endif %}
                {% endfor %}
                
                {% if posts.has_next %}
                <li class="page-item">
                    <a class="page-link" href="{{ url_for('index', page=posts.next_num) }}">下一页</a>
                </li>
                {% endif %}
            </ul>
        </nav>
        {% endif %}
    </div>
    
    <div class="col-md-4">
        <div class="card">
            <div class="card-header">
                关于博客
            </div>
            <div class="card-body">
                <p>这是一个使用Flask构建的个人博客系统。你可以在这里发布文章、阅读他人的文章并发表评论。</p>
                <p>如果你还没有账号,请先<a href="{{ url_for('register') }}">注册</a>一个。</p>
            </div>
        </div>
    </div>
</div>
{% endblock %}

<!-- templates/post_detail.html -->
{% extends "base.html" %}

{% block title %}{{ post.title }} - 个人博客{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8">
        <article>
            <h1>{{ post.title }}</h1>
            <p class="text-muted">
                作者: {{ post.author.username }} | 
                发布时间: {{ post.created_at.strftime('%Y-%m-%d %H:%M') }}
                {% if post.updated_at != post.created_at %}
                | 更新时间: {{ post.updated_at.strftime('%Y-%m-%d %H:%M') }}
                {% endif %}
            </p>
            <hr>
            <div>
                {{ post.content | safe }}
            </div>
            
            {% if session.user_id and post.user_id == session.user_id %}
            <div class="mt-4">
                <a href="{{ url_for('edit_post', post_id=post.id) }}" class="btn btn-primary">编辑</a>
                <form method="POST" action="{{ url_for('delete_post', post_id=post.id) }}" class="d-inline" onsubmit="return confirm('确定要删除这篇文章吗?');">
                    <button type="submit" class="btn btn-danger">删除</button>
                </form>
            </div>
            {% endif %}
        </article>
        
        <hr>
        
        <section>
            <h3>评论 ({{ comments|length }})</h3>
            
            {% if comments %}
            {% for comment in comments %}
            <div class="comment">
                <div class="comment-content">{{ comment.content }}</div>
                <div class="comment-meta">
                    {{ comment.author.username }} - {{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}
                </div>
            </div>
            {% endfor %}
            {% else %}
            <p>暂无评论。</p>
            {% endif %}
            
            {% if session.user_id %}
            <div class="mt-4">
                <h4>发表评论</h4>
                <form method="POST" action="{{ url_for('add_comment', post_id=post.id) }}">
                    <div class="mb-3">
                        <textarea name="content" class="form-control" rows="3" required></textarea>
                    </div>
                    <button type="submit" class="btn btn-primary">提交评论</button>
                </form>
            </div>
            {% else %}
            <p>请<a href="{{ url_for('login') }}">登录</a>后发表评论。</p>
            {% endif %}
        </section>
    </div>
    
    <div class="col-md-4">
        <div class="card">
            <div class="card-header">
                文章作者
            </div>
            <div class="card-body">
                <h5>{{ post.author.username }}</h5>
                <p>注册时间: {{ post.author.created_at.strftime('%Y-%m-%d') }}</p>
                <p>文章数: {{ post.author.posts|length }}</p>
                <p>评论数: {{ post.author.comments|length }}</p>
            </div>
        </div>
    </div>
</div>
{% endblock %}

<!-- templates/create_post.html -->
{% extends "base.html" %}

{% block title %}写文章 - 个人博客{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8">
        <h2>写文章</h2>
        
        <form method="POST">
            <div class="mb-3">
                <label for="title" class="form-label">标题</label>
                <input type="text" class="form-control" id="title" name="title" required>
            </div>
            
            <div class="mb-3">
                <label for="content" class="form-label">内容</label>
                <textarea class="form-control" id="content" name="content" rows="10" required></textarea>
            </div>
            
            <button type="submit" class="btn btn-primary">发布文章</button>
            <a href="{{ url_for('index') }}" class="btn btn-secondary">取消</a>
        </form>
    </div>
    
    <div class="col-md-4">
        <div class="card">
            <div class="card-header">
                写作提示
            </div>
            <div class="card-body">
                <ul>
                    <li>标题应该简洁明了,能够概括文章的主要内容。</li>
                    <li>内容应该结构清晰,段落分明。</li>
                    <li>可以使用Markdown语法来格式化文本。</li>
                    <li>发布后,你仍然可以编辑或删除文章。</li>
                </ul>
            </div>
        </div>
    </div>
</div>

{% block scripts %}
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
    document.addEventListener('DOMContentLoaded', function() {
        const contentTextarea = document.getElementById('content');
        const previewDiv = document.createElement('div');
        previewDiv.className = 'mt-3 p-3 border rounded';
        previewDiv.innerHTML = '<h5>预览</h5><div id="preview-content"></div>';
        contentTextarea.parentNode.insertBefore(previewDiv, contentTextarea.nextSibling);
        
        const previewContent = document.getElementById('preview-content');
        
        function updatePreview() {
            const markdownText = contentTextarea.value;
            const htmlText = marked.parse(markdownText);
            previewContent.innerHTML = htmlText;
        }
        
        contentTextarea.addEventListener('input', updatePreview);
        updatePreview();
    });
</script>
{% endblock %}
{% endblock %}

<!-- templates/login.html -->
{% extends "base.html" %}

{% block title %}登录 - 个人博客{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card">
            <div class="card-header">
                <h2>登录</h2>
            </div>
            <div class="card-body">
                <form method="POST">
                    <div class="mb-3">
                        <label for="username" class="form-label">用户名</label>
                        <input type="text" class="form-control" id="username" name="username" required>
                    </div>
                    
                    <div class="mb-3">
                        <label for="password" class="form-label">密码</label>
                        <input type="password" class="form-control" id="password" name="password" required>
                    </div>
                    
                    <button type="submit" class="btn btn-primary">登录</button>
                    <a href="{{ url_for('register') }}" class="btn btn-link">还没有账号?注册</a>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}
相关推荐
AI 嗯啦4 小时前
Python 爬虫案例:爬取豆瓣电影 Top250 数据
开发语言·爬虫·python
云天徽上5 小时前
【数据可视化-104】安徽省2025年上半年GDP数据可视化分析:用Python和Pyecharts打造炫酷大屏
开发语言·python·信息可视化·数据分析·数据可视化
深瞳智检5 小时前
深度学习环境搭建运行(一) Ubuntu22.04 系统安装 CUDA11.8 和 CUDNN8.6.0 详细步骤(新手入门)
人工智能·python·深度学习·yolo·计算机视觉
大学生毕业题目5 小时前
毕业项目推荐:64-基于yolov8/yolov5/yolo11的蝴蝶种类检测识别系统(Python+卷积神经网络)
人工智能·python·yolo·目标检测·cnn·pyqt·蝴蝶检测
m0_578267865 小时前
从零开始的python学习(九)P134+P135+P136+P137+P138+P139+P140
开发语言·python·学习
@TsUnAmI~5 小时前
基于Flask的企业级产品信息管理系统技术实现笔记
笔记·python·flask
程序员的世界你不懂5 小时前
【Flask】测试平台开发,开发实现应用搜索和分页-第十篇
后端·python·flask
程序员的世界你不懂5 小时前
【Flask】测试平台开发,实现全局邮件发送工具 第十二篇
网络·python·flask
软糖工程0015 小时前
python中的分代垃圾回收机制的原理【python进阶二、2】
python·算法