零基础学Python——第八章:实战项目(4-5)

第八章:实战项目

8.4 GUI应用开发

8.4.1 GUI开发基础

  • GUI编程概述

    图形用户界面(GUI)使程序更加直观易用,Python提供了多种GUI开发库,如Tkinter、PyQt、wxPython等。

    # GUI开发的基本流程
    # 1. 创建主窗口
    # 2. 添加控件(按钮、标签、输入框等)
    # 3. 设置布局
    # 4. 定义事件处理函数
    # 5. 启动主循环
    
  • Tkinter基础

    Tkinter是Python标准库中的GUI工具包,简单易用,适合开发小型应用程序。

    import tkinter as tk
    from tkinter import messagebox
    
    # 创建主窗口
    root = tk.Tk()
    root.title("我的第一个GUI应用")
    root.geometry("400x300")  # 设置窗口大小
    
    # 添加标签
    label = tk.Label(root, text="你好,这是一个简单的GUI程序", font=("宋体", 14))
    label.pack(pady=20)
    
    # 添加输入框
    entry = tk.Entry(root, width=30)
    entry.pack(pady=10)
    
    # 定义按钮点击事件处理函数
    def on_button_click():
        name = entry.get()
        if name:
            messagebox.showinfo("问候", f"你好,{name}!欢迎使用我的程序。")
        else:
            messagebox.showwarning("警告", "请输入你的名字!")
    
    # 添加按钮
    button = tk.Button(root, text="点击问候", command=on_button_click, bg="#4CAF50", fg="white")
    button.pack(pady=10)
    
    # 启动主循环
    root.mainloop()
    
  • 布局管理

    GUI程序需要合理安排控件的位置和大小,Tkinter提供了几种布局管理器。

    import tkinter as tk
    
    root = tk.Tk()
    root.title("布局管理示例")
    
    # Frame是一个容器控件,用于组织其他控件
    frame = tk.Frame(root, padx=10, pady=10)
    frame.pack(padx=20, pady=20)
    
    # 使用Grid布局(网格布局)
    tk.Label(frame, text="用户名:").grid(row=0, column=0, sticky="e", pady=5)
    tk.Entry(frame).grid(row=0, column=1, pady=5)
    
    tk.Label(frame, text="密码:").grid(row=1, column=0, sticky="e", pady=5)
    password_entry = tk.Entry(frame, show="*")  # 密码输入框,显示*
    password_entry.grid(row=1, column=1, pady=5)
    
    # 使用Pack布局(包装布局)
    button_frame = tk.Frame(frame)
    button_frame.grid(row=2, column=0, columnspan=2, pady=10)
    
    tk.Button(button_frame, text="登录").pack(side="left", padx=5)
    tk.Button(button_frame, text="取消").pack(side="left", padx=5)
    
    # 使用Place布局(绝对定位)
    status_label = tk.Label(root, text="状态: 就绪", bd=1, relief="sunken", anchor="w")
    status_label.pack(side="bottom", fill="x")
    
    root.mainloop()
    
  • 事件处理

    GUI程序通过事件驱动,需要定义各种事件的处理函数。

    import tkinter as tk
    
    root = tk.Tk()
    root.title("事件处理示例")
    
    # 创建一个画布
    canvas = tk.Canvas(root, width=400, height=300, bg="white")
    canvas.pack(pady=10)
    
    # 当前绘图状态
    drawing = False
    last_x, last_y = 0, 0
    
    # 鼠标按下事件处理函数
    def on_mouse_down(event):
        global drawing, last_x, last_y
        drawing = True
        last_x, last_y = event.x, event.y
    
    # 鼠标移动事件处理函数
    def on_mouse_move(event):
        global drawing, last_x, last_y
        if drawing:
            canvas.create_line(last_x, last_y, event.x, event.y, width=2)
            last_x, last_y = event.x, event.y
    
    # 鼠标释放事件处理函数
    def on_mouse_up(event):
        global drawing
        drawing = False
    
    # 清除画布事件处理函数
    def clear_canvas():
        canvas.delete("all")
    
    # 绑定鼠标事件
    canvas.bind("<Button-1>", on_mouse_down)  # 鼠标左键按下
    canvas.bind("<B1-Motion>", on_mouse_move)  # 鼠标左键按下并移动
    canvas.bind("<ButtonRelease-1>", on_mouse_up)  # 鼠标左键释放
    
    # 添加清除按钮
    clear_button = tk.Button(root, text="清除画布", command=clear_canvas)
    clear_button.pack(pady=10)
    
    root.mainloop()
    

8.4.2 待办事项管理器项目

  • 项目需求分析

    我们将开发一个图形界面的待办事项管理器,允许用户添加、编辑、完成和删除待办事项。

    # 待办事项管理器的核心功能
    # 1. 添加新待办事项
    # 2. 编辑现有待办事项
    # 3. 标记待办事项为已完成
    # 4. 删除待办事项
    # 5. 保存待办事项到文件
    # 6. 从文件加载待办事项
    
  • 界面设计

    设计一个简洁直观的用户界面,包括待办事项列表、添加和编辑表单等。

    import tkinter as tk
    from tkinter import ttk, messagebox, simpledialog
    import json
    import os
    from datetime import datetime
    
    class TodoApp:
        def __init__(self, root):
            self.root = root
            self.root.title("待办事项管理器")
            self.root.geometry("600x450")
            self.root.resizable(True, True)
            
            # 数据存储
            self.todos = []
            self.data_file = "todos.json"
            
            # 创建界面
            self.create_ui()
            
            # 加载数据
            self.load_todos()
        
        def create_ui(self):
            """创建用户界面"""
            # 创建主框架
            main_frame = ttk.Frame(self.root, padding="10")
            main_frame.pack(fill=tk.BOTH, expand=True)
            
            # 创建标题标签
            title_label = ttk.Label(main_frame, text="待办事项管理器", font=("宋体", 16, "bold"))
            title_label.pack(pady=10)
            
            # 创建按钮框架
            button_frame = ttk.Frame(main_frame)
            button_frame.pack(fill=tk.X, pady=5)
            
            # 添加按钮
            add_button = ttk.Button(button_frame, text="添加待办事项", command=self.add_todo)
            add_button.pack(side=tk.LEFT, padx=5)
            
            edit_button = ttk.Button(button_frame, text="编辑", command=self.edit_todo)
            edit_button.pack(side=tk.LEFT, padx=5)
            
            complete_button = ttk.Button(button_frame, text="标记完成", command=self.toggle_complete)
            complete_button.pack(side=tk.LEFT, padx=5)
            
            delete_button = ttk.Button(button_frame, text="删除", command=self.delete_todo)
            delete_button.pack(side=tk.LEFT, padx=5)
            
            # 创建待办事项列表
            list_frame = ttk.Frame(main_frame)
            list_frame.pack(fill=tk.BOTH, expand=True, pady=10)
            
            # 创建滚动条
            scrollbar = ttk.Scrollbar(list_frame)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            
            # 创建Treeview控件
            columns = ("id", "title", "due_date", "status")
            self.todo_tree = ttk.Treeview(list_frame, columns=columns, show="headings", yscrollcommand=scrollbar.set)
            
            # 设置列标题
            self.todo_tree.heading("id", text="ID")
            self.todo_tree.heading("title", text="标题")
            self.todo_tree.heading("due_date", text="截止日期")
            self.todo_tree.heading("status", text="状态")
            
            # 设置列宽
            self.todo_tree.column("id", width=50)
            self.todo_tree.column("title", width=300)
            self.todo_tree.column("due_date", width=100)
            self.todo_tree.column("status", width=100)
            
            # 绑定双击事件
            self.todo_tree.bind("<Double-1>", lambda event: self.edit_todo())
            
            # 放置Treeview
            self.todo_tree.pack(fill=tk.BOTH, expand=True)
            
            # 配置滚动条
            scrollbar.config(command=self.todo_tree.yview)
            
            # 创建状态栏
            self.status_var = tk.StringVar()
            self.status_var.set("就绪")
            status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
            status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        def load_todos(self):
            """从文件加载待办事项"""
            try:
                if os.path.exists(self.data_file):
                    with open(self.data_file, "r", encoding="utf-8") as f:
                        self.todos = json.load(f)
                    self.update_todo_list()
                    self.status_var.set(f"已加载 {len(self.todos)} 个待办事项")
            except Exception as e:
                messagebox.showerror("错误", f"加载数据时出错: {e}")
                self.todos = []
        
        def save_todos(self):
            """保存待办事项到文件"""
            try:
                with open(self.data_file, "w", encoding="utf-8") as f:
                    json.dump(self.todos, f, ensure_ascii=False, indent=2)
                self.status_var.set(f"已保存 {len(self.todos)} 个待办事项")
            except Exception as e:
                messagebox.showerror("错误", f"保存数据时出错: {e}")
        
        def update_todo_list(self):
            """更新待办事项列表显示"""
            # 清空列表
            for item in self.todo_tree.get_children():
                self.todo_tree.delete(item)
            
            # 添加待办事项到列表
            for todo in self.todos:
                status = "已完成" if todo.get("completed", False) else "未完成"
                self.todo_tree.insert("", tk.END, values=(
                    todo.get("id", ""),
                    todo.get("title", ""),
                    todo.get("due_date", ""),
                    status
                ))
        
        def add_todo(self):
            """添加新待办事项"""
            # 创建对话框
            dialog = tk.Toplevel(self.root)
            dialog.title("添加待办事项")
            dialog.geometry("400x200")
            dialog.resizable(False, False)
            dialog.transient(self.root)  # 设置为主窗口的子窗口
            dialog.grab_set()  # 模态对话框
            
            # 创建表单
            ttk.Label(dialog, text="标题:").grid(row=0, column=0, sticky=tk.W, padx=10, pady=10)
            title_entry = ttk.Entry(dialog, width=30)
            title_entry.grid(row=0, column=1, padx=10, pady=10)
            title_entry.focus_set()  # 设置焦点
            
            ttk.Label(dialog, text="描述:").grid(row=1, column=0, sticky=tk.W, padx=10, pady=10)
            desc_entry = ttk.Entry(dialog, width=30)
            desc_entry.grid(row=1, column=1, padx=10, pady=10)
            
            ttk.Label(dialog, text="截止日期 (YYYY-MM-DD):").grid(row=2, column=0, sticky=tk.W, padx=10, pady=10)
            due_date_entry = ttk.Entry(dialog, width=30)
            due_date_entry.grid(row=2, column=1, padx=10, pady=10)
            due_date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
            
            # 保存函数
            def save_todo():
                title = title_entry.get().strip()
                if not title:
                    messagebox.showwarning("警告", "标题不能为空!")
                    return
                
                # 创建新待办事项
                todo_id = 1
                if self.todos:
                    todo_id = max(todo.get("id", 0) for todo in self.todos) + 1
                
                new_todo = {
                    "id": todo_id,
                    "title": title,
                    "description": desc_entry.get().strip(),
                    "due_date": due_date_entry.get().strip(),
                    "completed": False,
                    "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                }
                
                self.todos.append(new_todo)
                self.save_todos()
                self.update_todo_list()
                dialog.destroy()
            
            # 按钮框架
            button_frame = ttk.Frame(dialog)
            button_frame.grid(row=3, column=0, columnspan=2, pady=20)
            
            ttk.Button(button_frame, text="保存", command=save_todo).pack(side=tk.LEFT, padx=10)
            ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)
        
        def edit_todo(self):
            """编辑选中的待办事项"""
            selected = self.todo_tree.selection()
            if not selected:
                messagebox.showinfo("提示", "请先选择一个待办事项")
                return
            
            # 获取选中项的ID
            item_id = self.todo_tree.item(selected[0], "values")[0]
            
            # 查找对应的待办事项
            todo = None
            for t in self.todos:
                if t.get("id") == int(item_id):
                    todo = t
                    break
            
            if not todo:
                return
            
            # 创建对话框
            dialog = tk.Toplevel(self.root)
            dialog.title("编辑待办事项")
            dialog.geometry("400x200")
            dialog.resizable(False, False)
            dialog.transient(self.root)
            dialog.grab_set()
            
            # 创建表单
            ttk.Label(dialog, text="标题:").grid(row=0, column=0, sticky=tk.W, padx=10, pady=10)
            title_entry = ttk.Entry(dialog, width=30)
            title_entry.grid(row=0, column=1, padx=10, pady=10)
            title_entry.insert(0, todo.get("title", ""))
            
            ttk.Label(dialog, text="描述:").grid(row=1, column=0, sticky=tk.W, padx=10, pady=10)
            desc_entry = ttk.Entry(dialog, width=30)
            desc_entry.grid(row=1, column=1, padx=10, pady=10)
            desc_entry.insert(0, todo.get("description", ""))
            
            ttk.Label(dialog, text="截止日期 (YYYY-MM-DD):").grid(row=2, column=0, sticky=tk.W, padx=10, pady=10)
            due_date_entry = ttk.Entry(dialog, width=30)
            due_date_entry.grid(row=2, column=1, padx=10, pady=10)
            due_date_entry.insert(0, todo.get("due_date", ""))
            
            # 保存函数
            def update_todo():
                title = title_entry.get().strip()
                if not title:
                    messagebox.showwarning("警告", "标题不能为空!")
                    return
                
                # 更新待办事项
                todo["title"] = title
                todo["description"] = desc_entry.get().strip()
                todo["due_date"] = due_date_entry.get().strip()
                todo["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                
                self.save_todos()
                self.update_todo_list()
                dialog.destroy()
            
            # 按钮框架
            button_frame = ttk.Frame(dialog)
            button_frame.grid(row=3, column=0, columnspan=2, pady=20)
            
            ttk.Button(button_frame, text="保存", command=update_todo).pack(side=tk.LEFT, padx=10)
            ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)
        
        def toggle_complete(self):
            """切换待办事项的完成状态"""
            selected = self.todo_tree.selection()
            if not selected:
                messagebox.showinfo("提示", "请先选择一个待办事项")
                return
            
            # 获取选中项的ID
            item_id = self.todo_tree.item(selected[0], "values")[0]
            
            # 查找对应的待办事项并切换状态
            for todo in self.todos:
                if todo.get("id") == int(item_id):
                    todo["completed"] = not todo.get("completed", False)
                    break
            
            self.save_todos()
            self.update_todo_list()
        
        def delete_todo(self):
            """删除选中的待办事项"""
            selected = self.todo_tree.selection()
            if not selected:
                messagebox.showinfo("提示", "请先选择一个待办事项")
                return
            
            # 确认删除
            if not messagebox.askyesno("确认", "确定要删除选中的待办事项吗?"):
                return
            
            # 获取选中项的ID
            item_id = self.todo_tree.item(selected[0], "values")[0]
            
            # 删除待办事项
            self.todos = [todo for todo in self.todos if todo.get("id") != int(item_id)]
            
            self.save_todos()
            self.update_todo_list()
    
  • 主程序

    最后,我们创建主程序,启动待办事项管理器。

    def main():
        root = tk.Tk()
        app = TodoApp(root)
        root.protocol("WM_DELETE_WINDOW", lambda: (app.save_todos(), root.destroy()))
        root.mainloop()
    
    if __name__ == "__main__":
        main()
    
  • 项目扩展思路

    这个待办事项管理器还可以进一步扩展:

    • 添加任务优先级和分类功能
    • 实现任务搜索和筛选功能
    • 添加提醒功能,在截止日期前提醒用户
    • 改进界面设计,添加主题切换功能
    • 添加数据统计和可视化功能,如任务完成率统计

8.5 Web应用入门

8.5.1 Web开发基础

  • Web应用架构

    Web应用通常由前端(客户端)和后端(服务器端)组成,前端负责用户界面,后端负责业务逻辑和数据处理。

    # Web应用的基本架构
    # 1. 前端:HTML、CSS、JavaScript
    # 2. 后端:服务器端语言(如Python)、Web框架
    # 3. 数据库:存储应用数据
    # 4. Web服务器:处理HTTP请求和响应
    
  • Flask框架介绍

    Flask是Python的一个轻量级Web框架,简单易学,适合开发小型Web应用。

    from flask import Flask
    
    # 创建Flask应用实例
    app = Flask(__name__)
    
    # 定义路由和视图函数
    @app.route('/')
    def home():
        return "<h1>Hello, Flask!</h1>"
    
    # 启动应用
    if __name__ == '__main__':
        app.run(debug=True)
    
  • 路由与视图

    Flask使用装饰器定义路由,将URL映射到视图函数。

    from flask import Flask
    
    app = Flask(__name__)
    
    # 基本路由
    @app.route('/')
    def home():
        return "<h1>首页</h1>"
    
    # 带参数的路由
    @app.route('/user/<username>')
    def show_user(username):
        return f"<h1>用户: {username}</h1>"
    
    # 指定HTTP方法
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        return "<h1>登录页面</h1>"
    
    if __name__ == '__main__':
        app.run(debug=True)
    
  • 模板与静态文件

    Flask使用Jinja2模板引擎渲染HTML页面,并支持静态文件(如CSS、JavaScript)。

    from flask import Flask, render_template
    
    app = Flask(__name__)
    
    @app.route('/hello/<name>')
    def hello(name):
        # 渲染模板,传递变量
        return render_template('hello.html', name=name)
    
    if __name__ == '__main__':
        app.run(debug=True)
    

    模板文件(hello.html):

    <!DOCTYPE html>
    <html>
    <head>
        <title>Hello Page</title>
        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    </head>
    <body>
        <h1>Hello, {{ name }}!</h1>
        <p>Welcome to my Flask application.</p>
    </body>
    </html>
    

8.5.2 个人博客系统项目

  • 项目需求分析

    我们将开发一个简单的个人博客系统,包括文章列表、文章详情、添加和编辑文章等功能。

    # 个人博客系统的核心功能
    # 1. 显示文章列表
    # 2. 显示文章详情
    # 3. 添加新文章
    # 4. 编辑现有文章
    # 5. 删除文章
    # 6. 简单的用户认证
    
  • 项目结构设计

    设计合理的项目结构,包括模板、静态文件和数据模型等。

    blog_app/
    ├── app.py              # 主应用文件
    ├── models.py           # 数据模型
    ├── static/             # 静态文件
    │   ├── css/            # CSS样式
    │   │   └── style.css
    │   └── js/             # JavaScript脚本
    │       └── main.js
    ├── templates/          # HTML模板
    │   ├── base.html       # 基础模板
    │   ├── index.html      # 首页模板
    │   ├── post.html       # 文章详情模板
    │   ├── create.html     # 创建文章模板
    │   └── edit.html       # 编辑文章模板
    └── instance/           # 实例文件夹
        └── blog.db         # SQLite数据库
    
  • 数据模型实现

    使用SQLite数据库和Flask-SQLAlchemy ORM来管理博客数据。

    # models.py
    from flask_sqlalchemy import SQLAlchemy
    from datetime import datetime
    
    db = SQLAlchemy()
    
    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)
        
        def __repr__(self):
            return f"Post('{self.title}', '{self.created_at}')"
    
    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        username = db.Column(db.String(20), unique=True, nullable=False)
        password = db.Column(db.String(60), nullable=False)
        
        def __repr__(self):
            return f"User('{self.username}')"
    
  • 应用主文件

    实现Flask应用的主文件,包括路由和视图函数。

    # app.py
    from flask import Flask, render_template, request, redirect, url_for, flash, session
    from flask_sqlalchemy import SQLAlchemy
    from werkzeug.security import generate_password_hash, check_password_hash
    import os
    from datetime import datetime
    
    # 创建应用实例
    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)
    
    # 定义模型
    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)
    
    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        username = db.Column(db.String(20), unique=True, nullable=False)
        password = db.Column(db.String(60), nullable=False)
    
    # 创建数据库表
    with app.app_context():
        db.create_all()
        # 添加默认用户
        if not User.query.filter_by(username='admin').first():
            default_user = User(username='admin', password=generate_password_hash('password'))
            db.session.add(default_user)
            db.session.commit()
    
    # 首页路由
    @app.route('/')
    def index():
        posts = Post.query.order_by(Post.created_at.desc()).all()
        return render_template('index.html', posts=posts)
    
    # 文章详情路由
    @app.route('/post/<int:post_id>')
    def post(post_id):
        post = Post.query.get_or_404(post_id)
        return render_template('post.html', post=post)
    
    # 创建文章路由
    @app.route('/create', methods=['GET', 'POST'])
    def create():
        if 'user_id' not in session:
            flash('请先登录', 'danger')
            return redirect(url_for('login'))
        
        if request.method == 'POST':
            title = request.form['title']
            content = request.form['content']
            
            if not title or not content:
                flash('标题和内容不能为空', 'danger')
            else:
                post = Post(title=title, content=content)
                db.session.add(post)
                db.session.commit()
                flash('文章创建成功', 'success')
                return redirect(url_for('index'))
        
        return render_template('create.html')
    
    # 编辑文章路由
    @app.route('/edit/<int:post_id>', methods=['GET', 'POST'])
    def edit(post_id):
        if 'user_id' not in session:
            flash('请先登录', 'danger')
            return redirect(url_for('login'))
        
        post = Post.query.get_or_404(post_id)
        
        if request.method == 'POST':
            title = request.form['title']
            content = request.form['content']
            
            if not title or not content:
                flash('标题和内容不能为空', 'danger')
            else:
                post.title = title
                post.content = content
                post.updated_at = datetime.utcnow()
                db.session.commit()
                flash('文章更新成功', 'success')
                return redirect(url_for('post', post_id=post.id))
        
        return render_template('edit.html', post=post)
    
    # 删除文章路由
    @app.route('/delete/<int:post_id>')
    def delete(post_id):
        if 'user_id' not in session:
            flash('请先登录', 'danger')
            return redirect(url_for('login'))
        
        post = Post.query.get_or_404(post_id)
        db.session.delete(post)
        db.session.commit()
        flash('文章已删除', 'success')
        return redirect(url_for('index'))
    
    # 登录路由
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            username = request.form['username']
            password = request.form['password']
            
            user = User.query.filter_by(username=username).first()
            
            if user and check_password_hash(user.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.pop('user_id', None)
        session.pop('username', None)
        flash('已登出', 'success')
        return redirect(url_for('index'))
    
    # 启动应用
    if __name__ == '__main__':
        app.run(debug=True)
    
  • 模板实现

    创建HTML模板,使用Bootstrap框架美化界面。

    基础模板(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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
        <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    </head>
    <body>
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
            <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>
                        {% if session.user_id %}
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('create') }}">写文章</a>
                        </li>
                        {% endif %}
                    </ul>
                    <ul class="navbar-nav">
                        {% if session.user_id %}
                        <li class="nav-item">
                            <span class="nav-link">你好,{{ session.username }}</span>
                        </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>
                        {% endif %}
                    </ul>
                </div>
            </div>
        </nav>
    
        <div class="container">
            {% 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>
    
        <footer class="bg-dark text-white text-center py-3 mt-5">
            <div class="container">
                <p class="mb-0">© 2023 个人博客系统 | 使用Flask构建</p>
            </div>
        </footer>
    
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <script src="{{ url_for('static', filename='js/main.js') }}"></script>
    </body>
    </html>
    

    首页模板(index.html):

    {% extends 'base.html' %}
    
    {% block title %}首页 - 个人博客{% endblock %}
    
    {% block content %}
    <h1 class="mb-4">最新文章</h1>
    
    {% if posts %}
        <div class="row">
            {% for post in posts %}
                <div class="col-md-6 mb-4">
                    <div class="card h-100">
                        <div class="card-body">
                            <h5 class="card-title">{{ post.title }}</h5>
                            <p class="card-text text-muted">{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</p>
                            <p class="card-text">{{ post.content[:150] }}{% if post.content|length > 150 %}...{% endif %}</p>
                            <a href="{{ url_for('post', post_id=post.id) }}" class="btn btn-primary">阅读全文</a>
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>
    {% else %}
        <div class="alert alert-info">暂无文章,请先添加。</div>
    {% endif %}
    {% endblock %}
    
  • 运行与测试

    运行Flask应用并测试各项功能。

    # 运行应用
    if __name__ == '__main__':
        app.run(debug=True, host='0.0.0.0', port=5000)
    

    要运行应用,执行以下命令:

    python app.py
    

    然后在浏览器中访问 http://localhost:5000 即可查看博客系统。

  • 项目扩展思路

    这个简单的博客系统还可以进一步扩展:

    • 添加评论功能
    • 实现文章分类和标签
    • 添加用户注册功能
    • 实现文章搜索功能
    • 添加文章访问统计
    • 实现文件上传功能,支持图片插入
    • 使用Markdown编辑器优化文章编辑体验
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值