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='📝')
相关推荐
笨鸟先飞,勤能补拙6 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能7 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂7 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study8 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境9 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境9 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1239 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102169 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb0010 小时前
一个项目带你入门AI应用开发01
python
05664610 小时前
Python康复训练——常用标准库
开发语言·python·学习