NiceGUI sqlite3 写一个博客系统 CURD 分类

图:

分类:

代码:database.py

python 复制代码
import sqlite3
from datetime import datetime

class BlogDB:
    def __init__(self, db_name='blog.db'):
        self.db_name = db_name
        self.init_db()
    
    def init_db(self):
        """初始化数据库表"""
        conn = sqlite3.connect(self.db_name)
        cursor = conn.cursor()
        
        # 创建分类表
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS categories (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL UNIQUE,
            description TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        ''')
        
        # 创建文章表
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS posts (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            category_id INTEGER,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE SET NULL
        )
        ''')
        
        # 创建评论表
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS comments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            post_id INTEGER,
            author TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE
        )
        ''')
        
        # 添加一个默认分类
        cursor.execute('''
        INSERT OR IGNORE INTO categories (name, description) VALUES (?, ?)
        ''', ('未分类', '默认分类'))
        
        conn.commit()
        conn.close()
    
    def get_connection(self):
        """获取数据库连接"""
        conn = sqlite3.connect(self.db_name)
        conn.row_factory = sqlite3.Row  # 返回字典格式
        return conn
    
    # 文章相关操作
    def create_post(self, title, content, category_id=None):
        """创建新文章"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'INSERT INTO posts (title, content, category_id) VALUES (?, ?, ?)',
            (title, content, category_id)
        )
        post_id = cursor.lastrowid
        conn.commit()
        conn.close()
        return post_id
    
    def get_all_posts(self, page=1, page_size=10):
        """获取所有文章,支持分页"""
        conn = self.get_connection()
        cursor = conn.cursor()
        offset = (page - 1) * page_size
        cursor.execute('SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?', (page_size, offset))
        posts = cursor.fetchall()
        conn.close()
        return posts
    
    def get_post_count(self):
        """获取文章总数"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('SELECT COUNT(*) FROM posts')
        count = cursor.fetchone()[0]
        conn.close()
        return count
    
    def get_post(self, post_id):
        """获取单篇文章"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM posts WHERE id = ?', (post_id,))
        post = cursor.fetchone()
        conn.close()
        # 将sqlite3.Row对象转换为字典
        return dict(post) if post else None
    
    def update_post(self, post_id, title, content, category_id=None):
        """更新文章"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'UPDATE posts SET title = ?, content = ?, category_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
            (title, content, category_id, post_id)
        )
        conn.commit()
        conn.close()
        return cursor.rowcount > 0
    
    def delete_post(self, post_id):
        """删除文章"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('DELETE FROM posts WHERE id = ?', (post_id,))
        conn.commit()
        conn.close()
        return cursor.rowcount > 0
    
    def search_posts(self, keyword, page=1, page_size=10):
        """根据关键词搜索文章,支持分页"""
        conn = self.get_connection()
        cursor = conn.cursor()
        # 使用LIKE操作符搜索标题和内容
        offset = (page - 1) * page_size
        cursor.execute('''
        SELECT * FROM posts 
        WHERE title LIKE ? OR content LIKE ? 
        ORDER BY created_at DESC
        LIMIT ? OFFSET ?
        ''', (f'%{keyword}%', f'%{keyword}%', page_size, offset))
        posts = cursor.fetchall()
        conn.close()
        return posts
    
    def get_search_count(self, keyword):
        """获取搜索结果的文章总数"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('''
        SELECT COUNT(*) FROM posts 
        WHERE title LIKE ? OR content LIKE ?
        ''', (f'%{keyword}%', f'%{keyword}%'))
        count = cursor.fetchone()[0]
        conn.close()
        return count
    
    # 分类相关操作
    def create_category(self, name, description=''):
        """创建分类"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'INSERT INTO categories (name, description) VALUES (?, ?)',
            (name, description)
        )
        category_id = cursor.lastrowid
        conn.commit()
        conn.close()
        return category_id
    
    def get_all_categories(self):
        """获取所有分类"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM categories ORDER BY created_at DESC')
        categories = cursor.fetchall()
        conn.close()
        # 将sqlite3.Row对象转换为字典
        return [dict(category) for category in categories]
    
    def get_category(self, category_id):
        """获取单个分类"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM categories WHERE id = ?', (category_id,))
        category = cursor.fetchone()
        conn.close()
        return category
    
    def update_category(self, category_id, name, description=''):
        """更新分类"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'UPDATE categories SET name = ?, description = ? WHERE id = ?',
            (name, description, category_id)
        )
        conn.commit()
        conn.close()
        return cursor.rowcount > 0
    
    def delete_category(self, category_id):
        """删除分类"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('DELETE FROM categories WHERE id = ?', (category_id,))
        conn.commit()
        conn.close()
        return cursor.rowcount > 0
    
    def get_posts_by_category(self, category_id, page=1, page_size=10):
        """根据分类获取文章,支持分页"""
        conn = self.get_connection()
        cursor = conn.cursor()
        offset = (page - 1) * page_size
        cursor.execute('''
        SELECT * FROM posts 
        WHERE category_id = ? 
        ORDER BY created_at DESC
        LIMIT ? OFFSET ?
        ''', (category_id, page_size, offset))
        posts = cursor.fetchall()
        conn.close()
        return posts
    
    def get_category_post_count(self, category_id):
        """获取分类下的文章总数"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute('SELECT COUNT(*) FROM posts WHERE category_id = ?', (category_id,))
        count = cursor.fetchone()[0]
        conn.close()
        return count
    
    # 评论相关操作
    def add_comment(self, post_id, author, content):
        """添加评论"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'INSERT INTO comments (post_id, author, content) VALUES (?, ?, ?)',
            (post_id, author, content)
        )
        conn.commit()
        conn.close()
    
    def get_comments(self, post_id):
        """获取文章的评论"""
        conn = self.get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'SELECT * FROM comments WHERE post_id = ? ORDER BY created_at DESC',
            (post_id,)
        )
        comments = cursor.fetchall()
        conn.close()
        return comments

主程序代码:main.py

python 复制代码
from nicegui import ui
import database

# 初始化数据库
db = database.BlogDB()

def create_blog_ui():
        """创建博客界面"""
        
        # 状态变量
        current_post_id = None
        edit_mode = False
        search_keyword = ''
        selected_category = None  # 选中的分类ID,None表示不筛选
        show_admin_buttons = False  # 控制编辑/删除按钮的显示,默认为隐藏
        current_page = 1
        page_size = 5  # 每页显示5篇文章
        total_posts = 0
    
    # 设置全局样式
        ui.query('body').style('background-color: #f8fafc; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;')
    
    # 主容器 - 使用固定宽度居中布局
        with ui.column().classes('w-full max-w-6xl mx-auto px-4 py-8'):
            # 顶部标题栏
            with ui.card().classes('w-full p-6 bg-gradient-to-r from-blue-600 to-indigo-700 shadow-lg mb-8 rounded-xl'):
                with ui.row().classes('w-full justify-between items-center'):
                    ui.label('📝 我的博客系统').classes('text-3xl font-bold text-white')
                    
                    with ui.row().classes('items-center gap-3'):
                        # 搜索和筛选区域
                        with ui.row().classes('items-center gap-2 bg-white bg-opacity-10 backdrop-blur-sm rounded-full px-3 py-1'):
                            # 搜索框
                            search_input = ui.input(placeholder='搜索文章...').classes('w-64 bg-transparent border-none text-white placeholder:text-white/70 focus:outline-none focus:ring-0')
                            
                            # 分类筛选下拉框
                            categories = db.get_all_categories()
                            category_options = {None: '全部分类'}
                            if categories:
                                category_options.update({category['id']: category['name'] for category in categories})
                            category_filter = ui.select(category_options, value=None).classes('w-48 bg-transparent border-none text-white focus:outline-none focus:ring-0')
                            category_filter.props('hide-selected-icon hide-dropdown-icon')
                            
                            def perform_search():
                                nonlocal search_keyword, selected_category, current_page
                                search_keyword = search_input.value.strip()
                                selected_category = category_filter.value
                                current_page = 1  # 搜索或筛选时重置到第一页
                                show_main_view()
                            
                            # 搜索按钮
                            ui.button('🔍', on_click=perform_search).classes('bg-white text-blue-700 hover:bg-blue-100 px-3 py-1 rounded-full transition-all')
                            
                            # 清空搜索
                            def clear_search():
                                nonlocal search_keyword, selected_category, current_page
                                search_keyword = ''
                                selected_category = None
                                search_input.value = ''
                                category_filter.value = None
                                current_page = 1  # 清空搜索时重置到第一页
                                show_main_view()
                            
                            ui.button('🗑️', on_click=clear_search).classes('bg-white/20 text-white hover:bg-white/30 px-3 py-1 rounded-full transition-all')
                        
                        # 操作按钮区域
                        with ui.row().classes('items-center gap-2'):
                            # 管理员控制按钮
                            def toggle_admin_buttons():
                                nonlocal show_admin_buttons
                                show_admin_buttons = not show_admin_buttons
                                show_main_view()  # 刷新视图以更新按钮可见性
                                
                            ui.button('⚙️ 管理模式', on_click=toggle_admin_buttons).classes(f'px-4 py-2 rounded-lg transition-all {"bg-purple-600 text-white hover:bg-purple-700 shadow-lg" if show_admin_buttons else "bg-purple-500 text-white hover:bg-purple-600"}')
                            
                            # 刷新列表
                            ui.button('🔄', on_click=lambda: clear_search()).classes('bg-gray-500 text-white hover:bg-gray-600 px-4 py-2 rounded-lg transition-all')
                            
                            # 分类管理按钮
                            def open_category_management():
                                category_dialog.open()
                            
                            ui.button('🏷️', on_click=open_category_management).classes('bg-orange-500 text-white hover:bg-orange-600 px-4 py-2 rounded-lg transition-all')
                            
                            # 写新文章
                            ui.button('✍️ 写新文章', on_click=lambda: show_create_post()).classes('bg-green-500 text-white hover:bg-green-600 px-4 py-2 rounded-lg font-semibold transition-all shadow-md hover:shadow-lg')
        
        # 分类管理对话框
        with ui.dialog() as category_dialog:
            with ui.card().classes('w-full max-w-4xl p-8 rounded-2xl shadow-xl border border-gray-100'):
                ui.label('🏷️ 分类管理').classes('text-3xl font-bold text-gradient-to-r from-orange-500 to-red-600 mb-8')
                
                # 分类列表容器
                categories_container = ui.column().classes('mb-8 gap-6')
                
                # 添加分类表单
                with ui.card().classes('w-full p-8 bg-gradient-to-br from-orange-50 to-red-50 rounded-xl shadow-md border border-gray-100 mb-8'):
                    ui.label('添加新分类').classes('text-2xl font-bold text-gray-800 mb-6')
                    
                    with ui.grid(columns=2).classes('gap-6 mb-6'):
                        name_input = ui.input(placeholder='分类名称').classes('w-full p-4 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all')
                        description_input = ui.textarea(placeholder='分类描述').classes('w-full p-4 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all resize-y')
                    
                    def add_category():
                        name = name_input.value.strip()
                        description = description_input.value.strip()
                        
                        if not name:
                            ui.notify('请输入分类名称', type='warning')
                            return
                        
                        try:
                            db.create_category(name, description)
                            ui.notify(f'分类 "{name}" 创建成功!', type='positive')
                            name_input.value = ''
                            description_input.value = ''
                            load_categories()
                        except Exception as e:
                            ui.notify(f'创建分类失败: {str(e)}', type='negative')
                    
                    ui.button('添加分类', on_click=add_category).classes('bg-gradient-to-r from-orange-500 to-red-600 text-white hover:from-orange-600 hover:to-red-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
                
                # 加载分类列表
                def load_categories():
                    categories_container.clear()
                    categories = db.get_all_categories()
                    
                    if not categories:
                        with categories_container:
                            with ui.card().classes('text-center p-10 bg-gradient-to-br from-orange-50 to-red-50 rounded-xl shadow-md border border-gray-100'):
                                ui.icon('tag', size='2xl').classes('text-orange-400 mb-4')
                                ui.label('暂无分类').classes('text-xl font-semibold text-gray-600 mb-2')
                                ui.label('请先添加分类').classes('text-gray-500')
                        return
                    
                    with categories_container:
                        with ui.grid(columns=1).classes('gap-6'):
                            for category in categories:
                                with ui.card().classes('w-full p-6 rounded-xl shadow-md hover:shadow-xl transition-all duration-300 hover:-translate-y-1 border border-gray-100'):
                                    with ui.row().classes('justify-between items-start'):
                                        with ui.column().classes('flex-grow'):
                                            ui.label(category['name']).classes('text-2xl font-bold text-gray-800')
                                            if category['description']:
                                                ui.label(category['description']).classes('text-gray-600 mt-2 text-lg leading-relaxed')
                                            ui.label(f'文章数量: {db.get_category_post_count(category["id"])}').classes('text-sm text-gray-500 mt-3 bg-orange-100 px-3 py-1 rounded-full inline-block')
                                        
                                        with ui.column(align_items='end'):
                                            # 编辑按钮
                                            def edit_category(category_id, category_name, category_desc):
                                                # 创建编辑对话框
                                                with ui.dialog() as edit_dialog:
                                                    with ui.card().classes('p-8 w-full max-w-md rounded-xl shadow-lg border border-gray-100'):
                                                        ui.label('编辑分类').classes('text-2xl font-bold text-gray-800 mb-6')
                                                         
                                                        edit_name_input = ui.input(value=category_name, placeholder='分类名称').classes('w-full p-4 border-2 border-gray-200 rounded-xl mb-6 focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all')
                                                        edit_desc_input = ui.textarea(value=category_desc, placeholder='分类描述').classes('w-full p-4 border-2 border-gray-200 rounded-xl mb-6 focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all resize-y')
                                                         
                                                        def save_edit():
                                                            new_name = edit_name_input.value.strip()
                                                            new_desc = edit_desc_input.value.strip()
                                                            
                                                            if not new_name:
                                                                ui.notify('请输入分类名称', type='warning')
                                                                return
                                                            
                                                            try:
                                                                db.update_category(category_id, new_name, new_desc)
                                                                ui.notify(f'分类更新成功!', type='positive')
                                                                edit_dialog.close()
                                                                load_categories()
                                                            except Exception as e:
                                                                ui.notify(f'更新分类失败: {str(e)}', type='negative')
                                                         
                                                        with ui.row().classes('justify-end gap-4'):
                                                            ui.button('取消', on_click=edit_dialog.close).classes('bg-gradient-to-r from-gray-500 to-slate-600 text-white hover:from-gray-600 hover:to-slate-700 px-6 py-3 rounded-lg transition-all shadow-md hover:shadow-lg')
                                                            ui.button('保存修改', on_click=save_edit).classes('bg-gradient-to-r from-orange-500 to-red-600 text-white hover:from-orange-600 hover:to-red-700 px-6 py-3 rounded-lg transition-all shadow-md hover:shadow-lg')
                                                edit_dialog.open()
                                            
                                            # 删除按钮
                                            def delete_category(category_id, category_name):
                                                # 确认删除
                                                def confirm_delete():
                                                    try:
                                                        # 检查是否有文章使用该分类
                                                        post_count = db.get_category_post_count(category_id)
                                                        if post_count > 0:
                                                            ui.notify(f'该分类下有 {post_count} 篇文章,无法删除', type='warning')
                                                            return
                                                        
                                                        db.delete_category(category_id)
                                                        ui.notify(f'分类 "{category_name}" 删除成功!', type='positive')
                                                        load_categories()
                                                    except Exception as e:
                                                        ui.notify(f'删除分类失败: {str(e)}', type='negative')
                                                
                                                with ui.dialog() as delete_dialog:
                                                    with ui.card().classes('p-8 w-96 rounded-xl shadow-lg border border-gray-100'):
                                                        ui.icon('warning', color='red', size='2xl').classes('mx-auto mb-6')
                                                        ui.label('确认删除').classes('text-2xl font-bold text-center mb-3')
                                                        ui.label(f'确定要删除分类 "{category_name}"吗?此操作不可恢复。').classes('text-gray-600 text-center mb-8')
                                                        with ui.row().classes('justify-center gap-4'):
                                                            ui.button('取消', on_click=delete_dialog.close).classes('bg-gradient-to-r from-gray-500 to-slate-600 text-white hover:from-gray-600 hover:to-slate-700 px-6 py-3 rounded-lg transition-all shadow-md hover:shadow-lg')
                                                            ui.button('确定删除', on_click=lambda: [delete_dialog.close(), confirm_delete()]).classes('bg-gradient-to-r from-red-500 to-rose-600 text-white hover:from-red-600 hover:to-rose-700 px-6 py-3 rounded-lg transition-all shadow-md hover:shadow-lg')
                                                delete_dialog.open()
                                            
                                            with ui.row().classes('gap-3 mt-2'):
                                                ui.button('✏️ 编辑', on_click=lambda c=category: edit_category(c['id'], c['name'], c['description'])).classes('bg-gradient-to-r from-yellow-500 to-amber-600 text-white hover:from-yellow-600 hover:to-amber-700 px-5 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                                                ui.button('🗑️ 删除', on_click=lambda c=category: delete_category(c['id'], c['name'])).classes('bg-gradient-to-r from-red-500 to-rose-600 text-white hover:from-red-600 hover:to-rose-700 px-5 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                
                # 加载初始分类列表
                load_categories()
        
        # 主内容区域
        main_container = ui.column().classes('w-full')
        edit_container = ui.column().classes('w-full')
        
        def show_main_view():
            """显示主视图(文章列表)"""
            edit_container.visible = False
            main_container.clear()
            main_container.visible = True
            
            nonlocal current_page, total_posts
            
            with main_container:
                # 页面标题和筛选条件
                if search_keyword or selected_category is not None:
                    with ui.row().classes('items-center mb-6 max-w-6xl mx-auto'):
                        ui.label('📰 博客文章列表').classes('text-2xl font-bold text-gray-800 mr-4')
                        
                        # 显示筛选条件
                        filters = []
                        if search_keyword:
                            filters.append(f'搜索:"{search_keyword}"')
                        if selected_category is not None:
                            category_name = db.get_category(selected_category)['name']
                            filters.append(f'分类:"{category_name}"')
                        
                        if filters:
                            ui.label(' | '.join(filters)).classes('text-lg font-semibold text-gray-600')
                else:
                    with ui.row().classes('items-center mb-6 max-w-6xl mx-auto'):
                        ui.label('📰 博客文章列表').classes('text-2xl font-bold text-gray-800 mb-6')
                
                # 文章列表
                if search_keyword:
                    # 获取搜索结果,支持分页
                    posts = db.search_posts(search_keyword, page=current_page, page_size=page_size)
                    total_posts = db.get_search_count(search_keyword)
                elif selected_category is not None:
                    # 按分类筛选
                    posts = db.get_posts_by_category(selected_category, page=current_page, page_size=page_size)
                    total_posts = db.get_category_post_count(selected_category)
                else:
                    # 获取所有文章,支持分页
                    posts = db.get_all_posts(page=current_page, page_size=page_size)
                    total_posts = db.get_post_count()
                
                if not posts:
                    with ui.card().classes('w-full p-8 text-center'):
                        ui.icon('edit_note', size='xl').classes('text-gray-400 mb-4')
                        ui.label('暂无文章').classes('text-xl text-gray-600 mb-2')
                        ui.label('点击"写新文章"按钮开始您的创作之旅').classes('text-gray-500')
                    return
                
                # 文章网格布局
                with ui.grid(columns=1).classes('w-full gap-6 max-w-6xl mx-auto'):
                    for post in posts:
                        with ui.card().classes('w-full overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all duration-300 hover:-translate-y-1 border border-gray-100'):
                            # 卡片头部 - 背景色区分
                            with ui.row().classes('justify-between items-start p-5 bg-gradient-to-r from-blue-50 to-indigo-50'):
                                ui.label(post['title']).classes('text-2xl font-bold text-gray-800 truncate flex-grow')
                                ui.label(f'📅 {post["created_at"]}').classes('text-sm text-gray-500 ml-4 whitespace-nowrap')
                            
                            # 卡片内容
                            with ui.column().classes('p-5'):
                                # 分类标签
                                category = db.get_category(post['category_id'])
                                with ui.row().classes('mb-4'):
                                    ui.label(f'🏷️ {category["name"]}').classes('px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium')
                                
                                # 文章内容预览
                                preview = post['content']
                                if len(preview) > 300:
                                    preview = preview[:300] + '...'
                                ui.label(preview).classes('text-gray-700 mb-6 leading-relaxed line-clamp-3')
                              
                                # 操作按钮区域
                                with ui.row().classes('justify-between items-center'):
                                    with ui.row().classes('gap-2'):
                                        ui.button('👁️ 查看详情', on_click=lambda p=post: show_post_detail(p['id'])).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-4 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                                      
                                        # 编辑按钮 - 仅在管理模式下显示
                                        if show_admin_buttons:
                                            ui.button('✏️ 编辑', on_click=lambda p=post: show_edit_post(p['id'])).classes('bg-gradient-to-r from-yellow-500 to-amber-600 text-white hover:from-yellow-600 hover:to-amber-700 px-4 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                                      
                                            # 删除按钮 - 带确认,仅在管理模式下显示
                                            def delete_with_confirm(p_id, p_title):
                                                def confirm_delete():
                                                    db.delete_post(p_id)
                                                    ui.notify(f'文章"{p_title}"已删除', type='positive')
                                                    # 重新计算总文章数
                                                    nonlocal total_posts, current_page
                                                    if search_keyword:
                                                        total_posts = db.get_search_count(search_keyword)
                                                    elif selected_category is not None:
                                                        total_posts = db.get_category_post_count(selected_category)
                                                    else:
                                                        total_posts = db.get_post_count()
                                                    # 如果当前页没有文章了,返回上一页
                                                    if total_posts < (current_page - 1) * page_size + 1:
                                                        current_page = max(1, current_page - 1)
                                                    show_main_view()
                                                
                                                # 使用对话框确认
                                                with ui.dialog() as dialog:
                                                    with ui.card().classes('p-6 w-96 rounded-xl shadow-lg'):
                                                        ui.icon('warning', color='red', size='xl').classes('mx-auto mb-4')
                                                        ui.label('确认删除').classes('text-xl font-bold text-center mb-2')
                                                        ui.label(f'确定要删除文章"{p_title}"吗?此操作不可恢复。').classes('text-gray-600 text-center mb-6')
                                                        with ui.row().classes('justify-center gap-4'):
                                                            ui.button('确定删除', on_click=lambda: [dialog.close(), confirm_delete()]).classes('bg-red-500 text-white hover:bg-red-600 px-4 py-2 rounded-lg transition-all')
                                                            ui.button('取消', on_click=dialog.close).classes('bg-gray-200 text-gray-700 hover:bg-gray-300 px-4 py-2 rounded-lg transition-all')
                                                dialog.open()
                                            
                                            ui.button('🗑️ 删除', on_click=lambda p=post: delete_with_confirm(p['id'], p['title'])).classes('bg-gradient-to-r from-red-500 to-rose-600 text-white hover:from-red-600 hover:to-rose-700 px-4 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                
                # 分页控件
                if total_posts > page_size:
                    total_pages = (total_posts + page_size - 1) // page_size
                    
                    # 分页按钮点击处理函数
                    def go_to_previous_page():
                        nonlocal current_page
                        if current_page > 1:
                            current_page -= 1
                            show_main_view()
                    
                    def go_to_next_page():
                        nonlocal current_page
                        if current_page < total_pages:
                            current_page += 1
                            show_main_view()
                    
                    def go_to_first_page():
                        nonlocal current_page
                        current_page = 1
                        show_main_view()
                    
                    def go_to_last_page():
                        nonlocal current_page
                        current_page = total_pages
                        show_main_view()
                    
                    with ui.row().classes('justify-center items-center gap-2 mt-8 max-w-6xl mx-auto'):
                        # 首页按钮
                        ui.button('首页', 
                                  on_click=go_to_first_page).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-4 py-2 rounded-lg text-sm transition-all shadow-sm hover:shadow-md')
                        
                        # 上一页按钮
                        ui.button('◀️ 上一页', 
                                  on_click=go_to_previous_page).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-4 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                        
                        # 页码显示
                        ui.label(f'第 {current_page} / {total_pages} 页').classes('text-gray-600 font-semibold px-4')
                        
                        # 下一页按钮
                        ui.button('下一页 ▶️', 
                                  on_click=go_to_next_page).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-4 py-2 rounded-lg transition-all shadow-sm hover:shadow-md')
                        
                        # 末页按钮
                        ui.button('末页', 
                                  on_click=go_to_last_page).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-4 py-2 rounded-lg text-sm transition-all shadow-sm hover:shadow-md')
        
        def show_create_post():
            """显示创建文章界面"""
            nonlocal current_post_id, edit_mode
            current_post_id = None
            edit_mode = True
            
            main_container.visible = False
            edit_container.clear()
            edit_container.visible = True
            
            with edit_container:
                # 页面标题
                with ui.row().classes('items-center mb-6 max-w-6xl mx-auto'):
                    ui.icon('add_circle', color='green', size='lg')
                    ui.label('创作新文章').classes('text-2xl font-bold text-gray-800 ml-2')
                
                # 表单区域
                with ui.card().classes('w-full p-8 rounded-xl shadow-md border border-gray-100 mb-8 max-w-6xl mx-auto'):
                    # 标题输入
                    ui.label('文章标题').classes('text-xl font-semibold text-gray-800 mb-3')
                    title_input = ui.input(placeholder='请输入文章标题...').classes('w-full p-4 border-2 border-gray-200 rounded-xl mb-6 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all')
                    
                    # 分类选择
                    ui.label('文章分类').classes('text-xl font-semibold text-gray-800 mb-3')
                    categories = db.get_all_categories()
                    if not categories:
                        # 如果没有分类,创建一个默认分类
                        db.create_category('未分类', '默认分类')
                        categories = db.get_all_categories()
                    
                    # 准备分类选项 (id: name)
                    category_options = {category['id']: category['name'] for category in categories}
                    # 强制转换为整数类型,确保与category_options的键类型一致
                    default_category_id = int(categories[0]['id'])
                    category_select = ui.select(category_options, value=default_category_id).classes('w-full p-4 border-2 border-gray-200 rounded-xl mb-6 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all')
                    category_select.props('hide-selected-icon')
                    
                    # 内容输入
                    ui.label('文章内容').classes('text-xl font-semibold text-gray-800 mb-3')
                    content_input = ui.textarea(placeholder='开始创作您的内容...').classes('w-full h-96 p-4 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all resize-y')
                    
                    # 操作按钮
                    with ui.row().classes('justify-end gap-4 mt-8'):
                        ui.button('保存文章', on_click=lambda: save_post(title_input, content_input, category_select)).classes('bg-gradient-to-r from-green-500 to-emerald-600 text-white hover:from-green-600 hover:to-emerald-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
                        ui.button('取消', on_click=show_main_view).classes('bg-gradient-to-r from-gray-500 to-slate-600 text-white hover:from-gray-600 hover:to-slate-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
        
        def show_edit_post(post_id):
            """显示编辑文章界面"""
            nonlocal current_post_id, edit_mode
            current_post_id = post_id
            edit_mode = True
            
            post = db.get_post(post_id)
            if not post:
                ui.notify('文章不存在', type='warning')
                show_main_view()
                return
            
            main_container.visible = False
            edit_container.clear()
            edit_container.visible = True
            
            with edit_container:
                # 页面标题
                with ui.row().classes('items-center mb-6 max-w-6xl mx-auto'):
                    ui.icon('edit', color='yellow', size='lg')
                    ui.label('编辑文章').classes('text-2xl font-bold text-gray-800 ml-2')
                
                # 表单区域
                with ui.card().classes('w-full p-8 rounded-xl shadow-md border border-gray-100 mb-8 max-w-6xl mx-auto'):
                    # 标题输入
                    ui.label('文章标题').classes('text-xl font-semibold text-gray-800 mb-3')
                    title_input = ui.input(placeholder='请输入文章标题...').classes('w-full p-4 border-2 border-gray-200 rounded-xl mb-6 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all')
                    title_input.value = post['title']
                    
                    # 分类选择
                    ui.label('文章分类').classes('text-xl font-semibold text-gray-800 mb-3')
                    categories = db.get_all_categories()
                    if not categories:
                        # 如果没有分类,创建一个默认分类
                        db.create_category('未分类', '默认分类')
                        categories = db.get_all_categories()
                    
                    # 准备分类选项 (id: name)
                    category_options = {category['id']: category['name'] for category in categories}
                    # 设置当前文章的分类
                    # 强制转换为整数类型,确保与category_options的键类型一致
                    if 'category_id' in post and post['category_id'] is not None:
                        current_category = int(post['category_id'])
                    else:
                        current_category = int(categories[0]['id']) if categories else None
                    
                    category_select = ui.select(category_options, value=current_category).classes('w-full p-4 border-2 border-gray-200 rounded-xl mb-6 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all')
                    category_select.props('hide-selected-icon')
                    
                    # 内容输入
                    ui.label('文章内容').classes('text-xl font-semibold text-gray-800 mb-3')
                    content_input = ui.textarea(placeholder='开始编辑您的内容...').classes('w-full h-96 p-4 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all resize-y')
                    content_input.value = post['content']
                    
                    # 操作按钮
                    with ui.row().classes('justify-end gap-4 mt-8'):
                        ui.button('保存修改', on_click=lambda: save_post(title_input, content_input, category_select)).classes('bg-gradient-to-r from-yellow-500 to-amber-600 text-white hover:from-yellow-600 hover:to-amber-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
                        ui.button('取消', on_click=show_main_view).classes('bg-gradient-to-r from-gray-500 to-slate-600 text-white hover:from-gray-600 hover:to-slate-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
        
        def save_post(title_input, content_input, category_select):
            """保存文章"""
            nonlocal current_post_id
            
            title = title_input.value.strip()
            content = content_input.value.strip()
            category_id = category_select.value
            
            if not title:
                ui.notify('请输入文章标题', type='warning')
                return
            
            if not content:
                ui.notify('请输入文章内容', type='warning')
                return
            
            try:
                if current_post_id is None:
                    # 创建新文章
                    db.create_post(title, content, category_id)
                    ui.notify('🎉 文章创建成功!', type='positive')
                else:
                    # 更新文章
                    db.update_post(current_post_id, title, content, category_id)
                    ui.notify('✅ 文章更新成功!', type='positive')
                
                show_main_view()
            except Exception as e:
                ui.notify(f'保存失败: {str(e)}', type='negative')
        
        def show_post_detail(post_id):
            """显示文章详情"""
            nonlocal current_post_id
            current_post_id = post_id
            
            post = db.get_post(post_id)
            if not post:
                ui.notify('文章不存在', type='warning')
                show_main_view()
                return
            
            main_container.visible = False
            edit_container.clear()
            edit_container.visible = True
            
            with edit_container:
                # 返回按钮和标题
                with ui.row().classes('justify-between items-center mb-8 max-w-6xl mx-auto'):
                    ui.button('← 返回列表', on_click=show_main_view).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-6 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
                    ui.label('文章详情').classes('text-3xl font-bold text-gray-800')
                
                # 文章内容卡片
                with ui.card().classes('w-full p-10 bg-white rounded-2xl shadow-lg border border-gray-100 mb-8 max-w-6xl mx-auto'):
                    # 文章标题
                    ui.label(post['title']).classes('text-4xl font-bold text-gray-900 mb-6 leading-tight')
                    
                    # 时间和分类信息
                    with ui.row().classes('items-center mb-8 p-4 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl'):
                        with ui.row().classes('items-center text-gray-600 mr-6'):
                            ui.icon('schedule', color='blue', size='md')
                            ui.label(f'发布时间: {post["created_at"]}').classes('ml-2 font-medium')
                          
                        # 分类信息
                        with ui.row().classes('items-center'):
                            ui.icon('label', color='blue', size='md')
                            if 'category_id' in post and post['category_id'] is not None:
                                category = db.get_category(post['category_id'])
                                category_name = category['name'] if category else '未分类'
                            else:
                                category_name = '未分类'
                            ui.label(f'分类: {category_name}').classes('ml-2 font-medium text-gray-800')
                    
                    # 文章内容
                    content_lines = post['content'].split('\n')
                    with ui.column().classes('gap-6'):
                        for line in content_lines:
                            if line.strip():  # 只显示非空行
                                ui.label(line).classes('text-gray-700 text-lg leading-relaxed')
                
                # 操作按钮区域
                with ui.card().classes('w-full p-8 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-md border border-gray-100 mb-8 max-w-6xl mx-auto'):
                    ui.label('文章操作').classes('text-2xl font-bold text-gray-800 mb-6')
                    with ui.row().classes('gap-4'):
                        # 编辑按钮 - 仅在管理模式下显示
                        if show_admin_buttons:
                            ui.button('✏️ 编辑文章', on_click=lambda: show_edit_post(post_id)).classes('bg-gradient-to-r from-yellow-500 to-amber-600 text-white hover:from-yellow-600 hover:to-amber-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
                            
                            # 删除按钮 - 仅在管理模式下显示
                            def delete_post():
                                def confirm_delete():
                                    db.delete_post(post_id)
                                    ui.notify('文章已删除', type='positive')
                                    show_main_view()
                                
                                # 确认对话框
                                with ui.dialog() as dialog:
                                    with ui.card().classes('p-8 w-96 rounded-xl shadow-lg border border-gray-100'):
                                        ui.icon('warning', color='red', size='2xl').classes('mx-auto mb-6')
                                        ui.label('确认删除').classes('text-2xl font-bold text-center mb-3')
                                        ui.label('确定要删除这篇文章吗?此操作不可恢复。').classes('text-gray-600 text-center mb-8')
                                        with ui.row().classes('justify-center gap-4'):
                                            ui.button('确定删除', on_click=lambda: [dialog.close(), confirm_delete()]).classes('bg-gradient-to-r from-red-500 to-rose-600 text-white hover:from-red-600 hover:to-rose-700 px-6 py-3 rounded-lg transition-all shadow-md hover:shadow-lg')
                                            ui.button('取消', on_click=dialog.close).classes('bg-gradient-to-r from-gray-500 to-slate-600 text-white hover:from-gray-600 hover:to-slate-700 px-6 py-3 rounded-lg transition-all shadow-md hover:shadow-lg')
                                dialog.open()
                            
                            ui.button('🗑️ 删除文章', on_click=delete_post).classes('bg-gradient-to-r from-red-500 to-rose-600 text-white hover:from-red-600 hover:to-rose-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg')
                
                # 评论区域
                with ui.card().classes('w-full p-8 bg-white rounded-2xl shadow-lg border border-gray-100 mb-8 max-w-6xl mx-auto'):
                    ui.label('💬 评论').classes('text-3xl font-bold text-gray-900 mb-8')
                    
                    # 添加评论表单
                    with ui.card().classes('w-full p-8 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-md border border-gray-100 mb-8'):
                        ui.label('添加新评论').classes('text-2xl font-bold text-gray-800 mb-6')
                        
                        with ui.grid(columns=1).classes('gap-6'):
                            comment_author = ui.input(placeholder='请输入您的名字').classes('w-full p-4 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all')
                            comment_content = ui.textarea(placeholder='请输入评论内容...').classes('w-full h-40 p-4 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all resize-y')
                        
                        def add_comment():
                            author = comment_author.value.strip()
                            content = comment_content.value.strip()
                            
                            if not author:
                                ui.notify('请输入名字', type='warning')
                                return
                            
                            if not content:
                                ui.notify('请输入评论内容', type='warning')
                                return
                            
                            try:
                                db.add_comment(post_id, author, content)
                                ui.notify('评论发表成功!', type='positive')
                                comment_author.value = ''
                                comment_content.value = ''
                                load_comments()
                            except Exception as e:
                                ui.notify(f'评论失败: {str(e)}', type='negative')
                        
                        ui.button('发表评论', on_click=add_comment).classes('bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 px-8 py-3 rounded-xl text-lg font-semibold transition-all shadow-md hover:shadow-lg mt-6')
                    
                    # 评论列表
                    comments_container = ui.column().classes('gap-4')
                    
                    def load_comments():
                        comments_container.clear()
                        comments = db.get_comments(post_id)
                        
                        if not comments:
                            with ui.card().classes('w-full text-center p-10 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-md border border-gray-100'):
                                ui.icon('chat', size='2xl').classes('text-blue-400 mb-4')
                                ui.label('暂无评论').classes('text-xl font-semibold text-gray-600 mb-2')
                                ui.label('快来发表第一条评论吧!').classes('text-gray-500')
                            return
                        
                        for comment in comments:
                            with comments_container:
                                with ui.card().classes('w-full p-6 rounded-xl shadow-md hover:shadow-lg transition-all duration-300 border border-gray-100'):
                                    # 评论头部
                                    with ui.row().classes('justify-between items-center mb-4'):
                                        with ui.row().classes('items-center'):
                                            with ui.card().classes('w-10 h-10 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-full flex items-center justify-center shadow-md'):
                                                ui.icon('person', color='white', size='md')
                                            ui.label(comment['author']).classes('font-bold text-xl text-gray-800 ml-3')
                                        ui.label(f'📅 {comment["created_at"]}').classes('text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full')
                                    
                                    # 评论内容
                                    ui.label(comment['content']).classes('text-gray-700 text-lg leading-relaxed mt-3 pl-13')
                    
                    # 初始加载评论
                    load_comments()
        
        # 初始显示主视图
        show_main_view()

# 创建界面
create_blog_ui()

# 运行应用
ui.run(title='📝 我的博客系统', port=8080, favicon='📝')
相关推荐
梦幻精灵_cq1 天前
python-os.path的“世纪大阅兵”:两个函数-11条语句(2+9)定乾坤
python·学习
AndrewHZ1 天前
【图像处理基石】RGB图像频域滤波:原理、实现与实战(Python)
图像处理·python·算法·计算机视觉·傅里叶变换·频域滤波·滤波核设计
Misnice1 天前
使用 SQLAlchemy 连接数据库
数据库·python·mysql·fastapi
龙腾AI白云1 天前
【循环神经网络讲解(3)】
python·深度学习
月亮!1 天前
当技术中立性遇上算法偏见:软件测试者的伦理启示
网络·人工智能·python·测试工具·算法·安全·开源
曲幽1 天前
Flask核心技能:从零上手视图函数
python·json·app·web·get·post·request·response
晞微1 天前
PyTorch 实现 BP 神经网络:从函数拟合到分类任务
pytorch·python·神经网络·分类
薛不痒1 天前
机器学习之Python中的numpy库,pandas库
开发语言·python
计算衎1 天前
FastAPI+ PostgreSQL+ VUE 实现一个数据平台展示案例
vue.js·python·postgresql·fastapi