nicegui 3.4.0 + sqlite3 做一个简单维修登记系统

主要功能:

  1. 新增维修登记 - 客户信息、设备信息和问题描述
  2. 状态管理 - 待处理、维修中、已完成三种状态
  3. 记录管理 - 查看、更新状态、删除记录
  4. 统计概览 - 实时统计各状态数量
  5. 查询功能 - 按客户、设备或问题描述搜索

数据库结构:

  • repair_records 表包含所有维修记录
  • 自动记录创建时间和完成时间
  • 预置了3条示例数据

界面特点: 响应式设计

  • 适配不同屏幕尺寸 颜色标识
  • 不同状态用不同颜色标签 操作友好
  • 清晰的按钮和提示 实时刷新
  • 操作后自动更新界面

代码:

python 复制代码
import sqlite3
import json
from datetime import datetime, date
from typing import Optional
from pathlib import Path

from nicegui import ui, app
from nicegui.events import ValueChangeEventArguments

# 数据库初始化
def init_database():
    """初始化数据库和表结构"""
    conn = sqlite3.connect('repair_system.db')
    cursor = conn.cursor()
    
    # 创建维修记录表
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS repair_records (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            customer_name TEXT NOT NULL,
            customer_phone TEXT NOT NULL,
            customer_email TEXT,
            device_name TEXT NOT NULL,
            device_type TEXT NOT NULL,
            device_serial TEXT,
            issue_description TEXT NOT NULL,
            status TEXT NOT NULL CHECK(status IN ('pending', 'in_progress', 'completed')),
            created_date DATE NOT NULL,
            completed_date DATE,
            notes TEXT
        )
    ''')
    
    # 检查表中是否有数据,如果没有则插入示例数据
    cursor.execute("SELECT COUNT(*) FROM repair_records")
    if cursor.fetchone()[0] == 0:
        # 插入示例数据
        sample_data = [
            ('张三', '13800138001', 'zhangsan@example.com', '笔记本电脑', '电脑', 'SN202301001', 
             '无法开机,按下电源键无反应', 'pending', '2023-10-01', None, '客户表示昨天使用正常,今天突然无法开机'),
            ('李四', '13900139002', 'lisi@example.com', '智能手机', '手机', 'SN202302001', 
             '屏幕破裂,触摸失灵', 'in_progress', '2023-10-02', None, '需要更换屏幕总成'),
            ('王五', '13700137003', 'wangwu@example.com', '激光打印机', '办公设备', 'SN202303001', 
             '打印质量差,有黑色条纹', 'completed', '2023-10-03', '2023-10-05', '已更换硒鼓,测试正常')
        ]
        
        cursor.executemany('''
            INSERT INTO repair_records 
            (customer_name, customer_phone, customer_email, device_name, device_type, 
             device_serial, issue_description, status, created_date, completed_date, notes)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', sample_data)
    
    conn.commit()
    conn.close()

# 初始化数据库
init_database()

# 状态映射
STATUS_MAP = {
    'pending': {'label': '待处理', 'color': 'orange'},
    'in_progress': {'label': '维修中', 'color': 'blue'},
    'completed': {'label': '已完成', 'color': 'green'}
}

# 设备类型选项
DEVICE_TYPES = ['电脑', '手机', '平板', '打印机', '扫描仪', '网络设备', '其他']

# 创建数据库连接
def get_db_connection():
    conn = sqlite3.connect('repair_system.db')
    conn.row_factory = sqlite3.Row
    return conn

class RepairSystem:
    def __init__(self):
        self.current_filter = None
        self.current_filter_value = None
        self.records_container = None
        self.stats_container = None
        
    def add_record(self):
        """新增维修记录"""
        with ui.dialog() as dialog, ui.card().classes('w-full max-w-2xl'):
            ui.label('新增维修登记').classes('text-h6 font-bold text-primary')
            
            with ui.column().classes('w-full gap-4'):
                # 客户信息
                with ui.card().classes('w-full'):
                    ui.label('客户信息').classes('text-subtitle1 font-bold')
                    customer_name = ui.input('客户姓名', validation={'请输入客户姓名': lambda value: bool(value)}).props('outlined').classes('w-full')
                    customer_phone = ui.input('联系电话', validation={'请输入有效的联系电话': lambda value: len(value) >= 8}).props('outlined').classes('w-full')
                    customer_email = ui.input('电子邮箱').props('outlined').classes('w-full')
                
                # 设备信息
                with ui.card().classes('w-full'):
                    ui.label('设备信息').classes('text-subtitle1 font-bold')
                    device_name = ui.input('设备名称', validation={'请输入设备名称': lambda value: bool(value)}).props('outlined').classes('w-full')
                    device_type = ui.select(DEVICE_TYPES, label='设备类型', value=DEVICE_TYPES[0]).props('outlined').classes('w-full')
                    device_serial = ui.input('设备序列号').props('outlined').classes('w-full')
                
                # 问题描述
                with ui.card().classes('w-full'):
                    ui.label('问题描述').classes('text-subtitle1 font-bold')
                    issue_description = ui.textarea('问题描述', validation={'请输入问题描述': lambda value: bool(value)}).props('outlined rows=3').classes('w-full')
                    notes = ui.textarea('备注').props('outlined rows=2').classes('w-full')
            
            with ui.row().classes('w-full justify-end gap-2 pt-4'):
                ui.button('取消', on_click=dialog.close).props('flat')
                ui.button('提交', on_click=lambda: self.save_record(
                    customer_name, customer_phone, customer_email, device_name, 
                    device_type, device_serial, issue_description, notes, dialog
                )).props('flat color=primary').bind_enabled_from(
                    customer_name, 'error', lambda e: not e
                ).bind_enabled_from(
                    customer_phone, 'error', lambda e: not e
                ).bind_enabled_from(
                    device_name, 'error', lambda e: not e
                ).bind_enabled_from(
                    issue_description, 'error', lambda e: not e
                )
        
        dialog.open()
    
    def save_record(self, customer_name, customer_phone, customer_email, device_name, 
                    device_type, device_serial, issue_description, notes, dialog):
        """保存维修记录到数据库"""
        # 验证输入
        if customer_name.value and customer_phone.value and device_name.value and issue_description.value:
            conn = get_db_connection()
            cursor = conn.cursor()
            
            cursor.execute('''
                INSERT INTO repair_records 
                (customer_name, customer_phone, customer_email, device_name, device_type, 
                 device_serial, issue_description, status, created_date, notes)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                customer_name.value,
                customer_phone.value,
                customer_email.value,
                device_name.value,
                device_type.value,
                device_serial.value,
                issue_description.value,
                'pending',  # 默认状态为待处理
                date.today().isoformat(),
                notes.value
            ))
            
            conn.commit()
            conn.close()
            
            ui.notify(f'维修记录已成功添加!', type='positive')
            dialog.close()
            self.refresh_records()
            self.update_stats()
        else:
            ui.notify('请填写所有必填字段!', type='warning')
    
    def update_status(self, record_id, new_status):
        """更新维修状态"""
        conn = get_db_connection()
        cursor = conn.cursor()
        
        if new_status == 'completed':
            cursor.execute('''
                UPDATE repair_records 
                SET status = ?, completed_date = ?
                WHERE id = ?
            ''', (new_status, date.today().isoformat(), record_id))
        else:
            cursor.execute('''
                UPDATE repair_records 
                SET status = ?, completed_date = NULL
                WHERE id = ?
            ''', (new_status, record_id))
        
        conn.commit()
        conn.close()
        
        status_label = STATUS_MAP[new_status]['label']
        ui.notify(f'状态已更新为 {status_label}', type='positive')
        self.refresh_records()
        self.update_stats()
    
    def delete_record(self, record_id, customer_name, device_name):
        """删除维修记录"""
        def confirm_delete():
            conn = get_db_connection()
            cursor = conn.cursor()
            cursor.execute('DELETE FROM repair_records WHERE id = ?', (record_id,))
            conn.commit()
            conn.close()
            
            ui.notify(f'已删除 {customer_name} 的 {device_name} 维修记录', type='positive')
            delete_dialog.close()
            self.refresh_records()
            self.update_stats()
        
        with ui.dialog() as delete_dialog, ui.card():
            ui.label(f'确认删除维修记录?').classes('text-h6')
            ui.label(f'客户:{customer_name},设备:{device_name}').classes('text-body1')
            
            with ui.row().classes('w-full justify-end gap-2 pt-4'):
                ui.button('取消', on_click=delete_dialog.close).props('flat')
                ui.button('确认删除', on_click=confirm_delete).props('flat color=negative')
        
        delete_dialog.open()
    
    def edit_record(self, record):
        """编辑维修记录"""
        with ui.dialog() as dialog, ui.card().classes('w-full max-w-2xl'):
            ui.label('编辑维修记录').classes('text-h6 font-bold text-primary')
            
            with ui.column().classes('w-full gap-4'):
                # 客户信息
                with ui.card().classes('w-full'):
                    ui.label('客户信息').classes('text-subtitle1 font-bold')
                    customer_name = ui.input('客户姓名', value=record['customer_name']).props('outlined').classes('w-full')
                    customer_phone = ui.input('联系电话', value=record['customer_phone']).props('outlined').classes('w-full')
                    customer_email = ui.input('电子邮箱', value=record['customer_email'] or '').props('outlined').classes('w-full')
                
                # 设备信息
                with ui.card().classes('w-full'):
                    ui.label('设备信息').classes('text-subtitle1 font-bold')
                    device_name = ui.input('设备名称', value=record['device_name']).props('outlined').classes('w-full')
                    device_type = ui.select(DEVICE_TYPES, label='设备类型', value=record['device_type']).props('outlined').classes('w-full')
                    device_serial = ui.input('设备序列号', value=record['device_serial'] or '').props('outlined').classes('w-full')
                
                # 问题描述
                with ui.card().classes('w-full'):
                    ui.label('问题描述').classes('text-subtitle1 font-bold')
                    issue_description = ui.textarea('问题描述', value=record['issue_description']).props('outlined rows=3').classes('w-full')
                    notes = ui.textarea('备注', value=record['notes'] or '').props('outlined rows=2').classes('w-full')
                
                # 状态
                with ui.card().classes('w-full'):
                    ui.label('维修状态').classes('text-subtitle1 font-bold')
                    status_select = ui.select(
                        list(STATUS_MAP.keys()),
                        label='状态',
                        value=record['status'],
                        format_func=lambda x: STATUS_MAP[x]['label']
                    ).props('outlined').classes('w-full')
            
            def save_edits():
                conn = get_db_connection()
                cursor = conn.cursor()
                
                cursor.execute('''
                    UPDATE repair_records 
                    SET customer_name = ?, customer_phone = ?, customer_email = ?,
                        device_name = ?, device_type = ?, device_serial = ?,
                        issue_description = ?, notes = ?, status = ?
                    WHERE id = ?
                ''', (
                    customer_name.value,
                    customer_phone.value,
                    customer_email.value,
                    device_name.value,
                    device_type.value,
                    device_serial.value,
                    issue_description.value,
                    notes.value,
                    status_select.value,
                    record['id']
                ))
                
                conn.commit()
                conn.close()
                
                ui.notify(f'维修记录已更新!', type='positive')
                dialog.close()
                self.refresh_records()
                self.update_stats()
            
            with ui.row().classes('w-full justify-end gap-2 pt-4'):
                ui.button('取消', on_click=dialog.close).props('flat')
                ui.button('保存更改', on_click=save_edits).props('flat color=primary')
        
        dialog.open()
    
    def show_record_details(self, record):
        """显示维修记录详情"""
        with ui.dialog() as dialog, ui.card().classes('w-full max-w-2xl'):
            ui.label('维修记录详情').classes('text-h6 font-bold text-primary')
            
            with ui.column().classes('w-full gap-4'):
                # 状态标签
                status_info = STATUS_MAP[record['status']]
                ui.label(status_info['label']).classes(f'text-white bg-{status_info["color"]}-500 px-4 py-1 rounded-full self-start')
                
                # 客户信息
                with ui.card().classes('w-full'):
                    ui.label('客户信息').classes('text-subtitle1 font-bold')
                    with ui.grid(columns=2).classes('w-full gap-2'):
                        ui.label('客户姓名:').classes('font-medium')
                        ui.label(record['customer_name'])
                        ui.label('联系电话:').classes('font-medium')
                        ui.label(record['customer_phone'])
                        if record['customer_email']:
                            ui.label('电子邮箱:').classes('font-medium')
                            ui.label(record['customer_email'])
                
                # 设备信息
                with ui.card().classes('w-full'):
                    ui.label('设备信息').classes('text-subtitle1 font-bold')
                    with ui.grid(columns=2).classes('w-full gap-2'):
                        ui.label('设备名称:').classes('font-medium')
                        ui.label(record['device_name'])
                        ui.label('设备类型:').classes('font-medium')
                        ui.label(record['device_type'])
                        if record['device_serial']:
                            ui.label('序列号:').classes('font-medium')
                            ui.label(record['device_serial'])
                
                # 维修信息
                with ui.card().classes('w-full'):
                    ui.label('维修信息').classes('text-subtitle1 font-bold')
                    with ui.grid(columns=2).classes('w-full gap-2'):
                        ui.label('创建日期:').classes('font-medium')
                        ui.label(record['created_date'])
                        if record['completed_date']:
                            ui.label('完成日期:').classes('font-medium')
                            ui.label(record['completed_date'])
                    ui.label('问题描述:').classes('font-medium pt-2')
                    ui.label(record['issue_description']).classes('text-body1')
                    if record['notes']:
                        ui.label('备注:').classes('font-medium pt-2')
                        ui.label(record['notes']).classes('text-body1')
            
            with ui.row().classes('w-full justify-end pt-4'):
                ui.button('关闭', on_click=dialog.close).props('flat')
        
        dialog.open()
    
    def filter_records(self, filter_type, value):
        """根据条件筛选记录"""
        self.current_filter = filter_type
        self.current_filter_value = value
        self.refresh_records()
    
    def clear_filter(self):
        """清除筛选条件"""
        self.current_filter = None
        self.current_filter_value = None
        self.refresh_records()
    
    def get_filtered_records(self):
        """获取筛选后的记录"""
        conn = get_db_connection()
        cursor = conn.cursor()
        
        if self.current_filter and self.current_filter_value:
            if self.current_filter == 'search':
                search_term = f'%{self.current_filter_value}%'
                cursor.execute('''
                    SELECT * FROM repair_records 
                    WHERE customer_name LIKE ? OR device_name LIKE ? OR issue_description LIKE ?
                    ORDER BY created_date DESC, id DESC
                ''', (search_term, search_term, search_term))
            elif self.current_filter == 'status':
                cursor.execute('''
                    SELECT * FROM repair_records 
                    WHERE status = ?
                    ORDER BY created_date DESC, id DESC
                ''', (self.current_filter_value,))
        else:
            cursor.execute('SELECT * FROM repair_records ORDER BY created_date DESC, id DESC')
        
        records = cursor.fetchall()
        conn.close()
        return records
    
    def refresh_records(self):
        """刷新维修记录列表"""
        if self.records_container:
            self.records_container.clear()
            
            records = self.get_filtered_records()
            
            if not records:
                with self.records_container:
                    ui.label('未找到维修记录').classes('text-body1 text-gray-500 text-center py-8')
                return
            
            for record in records:
                with self.records_container:
                    status_info = STATUS_MAP[record['status']]
                    
                    with ui.card().classes('w-full mb-4').tight():
                        # 卡片头部
                        with ui.row().classes('w-full items-center justify-between bg-gray-50 p-4'):
                            with ui.row().classes('items-center gap-4'):
                                ui.label(f'#{record["id"]}').classes('font-bold text-gray-700')
                                ui.label(record['customer_name']).classes('font-bold text-lg')
                                ui.label(record['device_name']).classes('text-gray-600')
                                ui.label(f'({record["device_type"]})').classes('text-gray-500')
                            
                            ui.label(status_info['label']).classes(f'text-white bg-{status_info["color"]}-500 px-3 py-1 rounded-full')
                        
                        # 卡片内容
                        with ui.column().classes('w-full p-4 gap-3'):
                            with ui.grid(columns=2).classes('w-full gap-2'):
                                ui.label('联系电话:').classes('font-medium text-gray-700')
                                ui.label(record['customer_phone'])
                                ui.label('创建日期:').classes('font-medium text-gray-700')
                                ui.label(record['created_date'])
                                if record['completed_date']:
                                    ui.label('完成日期:').classes('font-medium text-gray-700')
                                    ui.label(record['completed_date'])
                            
                            ui.label('问题描述:').classes('font-medium text-gray-700 pt-2')
                            ui.label(record['issue_description'][:100] + ('...' if len(record['issue_description']) > 100 else '')).classes('text-body1')
                            
                            if record['notes']:
                                ui.label('备注:').classes('font-medium text-gray-700 pt-2')
                                ui.label(record['notes'][:80] + ('...' if len(record['notes']) > 80 else '')).classes('text-body1 text-gray-600')
                        
                        # 卡片底部 - 操作按钮
                        with ui.row().classes('w-full justify-between bg-gray-50 p-3 border-t'):
                            with ui.row().classes('gap-2'):
                                ui.button('查看详情', on_click=lambda r=record: self.show_record_details(dict(r))).props('flat dense')
                                ui.button('编辑', on_click=lambda r=record: self.edit_record(dict(r))).props('flat dense color=primary')
                            
                            with ui.row().classes('gap-2'):
                                # 状态切换按钮
                                if record['status'] != 'pending':
                                    ui.button('设为待处理', on_click=lambda r=record: self.update_status(r['id'], 'pending')).props('flat dense color=orange')
                                if record['status'] != 'in_progress':
                                    ui.button('设为维修中', on_click=lambda r=record: self.update_status(r['id'], 'in_progress')).props('flat dense color=blue')
                                if record['status'] != 'completed':
                                    ui.button('设为已完成', on_click=lambda r=record: self.update_status(r['id'], 'completed')).props('flat dense color=green')
                                
                                ui.button('删除', on_click=lambda r=record: self.delete_record(r['id'], r['customer_name'], r['device_name'])).props('flat dense color=negative')
    
    def update_stats(self):
        """更新统计概览"""
        if self.stats_container:
            self.stats_container.clear()
            
            conn = get_db_connection()
            cursor = conn.cursor()
            
            # 获取各状态统计
            cursor.execute("SELECT status, COUNT(*) FROM repair_records GROUP BY status")
            status_counts = dict(cursor.fetchall())
            
            # 获取今日新增
            cursor.execute("SELECT COUNT(*) FROM repair_records WHERE created_date = ?", (date.today().isoformat(),))
            today_new = cursor.fetchone()[0]
            
            # 获取本月新增
            first_day_of_month = date.today().replace(day=1).isoformat()
            cursor.execute("SELECT COUNT(*) FROM repair_records WHERE created_date >= ?", (first_day_of_month,))
            month_new = cursor.fetchone()[0]
            
            conn.close()
            
            with self.stats_container:
                with ui.row().classes('w-full gap-4'):
                    with ui.card().classes('flex-1 bg-blue-50').tight():
                        with ui.column().classes('items-center justify-center p-4'):
                            ui.label('待处理').classes('text-lg font-medium text-gray-700')
                            ui.label(str(status_counts.get('pending', 0))).classes('text-3xl font-bold text-blue-600')
                    
                    with ui.card().classes('flex-1 bg-blue-50').tight():
                        with ui.column().classes('items-center justify-center p-4'):
                            ui.label('维修中').classes('text-lg font-medium text-gray-700')
                            ui.label(str(status_counts.get('in_progress', 0))).classes('text-3xl font-bold text-blue-600')
                    
                    with ui.card().classes('flex-1 bg-green-50').tight():
                        with ui.column().classes('items-center justify-center p-4'):
                            ui.label('已完成').classes('text-lg font-medium text-gray-700')
                            ui.label(str(status_counts.get('completed', 0))).classes('text-3xl font-bold text-green-600')
                    
                    with ui.card().classes('flex-1 bg-purple-50').tight():
                        with ui.column().classes('items-center justify-center p-4'):
                            ui.label('今日新增').classes('text-lg font-medium text-gray-700')
                            ui.label(str(today_new)).classes('text-3xl font-bold text-purple-600')
                    
                    with ui.card().classes('flex-1 bg-purple-50').tight():
                        with ui.column().classes('items-center justify-center p-4'):
                            ui.label('本月新增').classes('text-lg font-medium text-gray-700')
                            ui.label(str(month_new)).classes('text-3xl font-bold text-purple-600')
    
    def create_ui(self):
        """创建主界面"""
        # 设置页面标题
        ui.page_title('维修登记管理系统')
        
        # 主容器
        with ui.column().classes('w-full max-w-7xl mx-auto p-4 gap-6'):
            # 标题栏
            with ui.row().classes('w-full items-center justify-between'):
                ui.label('维修登记管理系统').classes('text-h4 font-bold text-primary')
                ui.button('新增维修登记', on_click=self.add_record, icon='add').props('color=primary')
            
            # 统计概览
            ui.label('统计概览').classes('text-h6 font-bold')
            self.stats_container = ui.row().classes('w-full')
            self.update_stats()
            
            # 操作和筛选栏
            with ui.card().classes('w-full'):
                with ui.row().classes('w-full items-center justify-between'):
                    # 状态筛选
                    with ui.row().classes('items-center gap-2'):
                        ui.label('状态筛选:').classes('font-medium')
                        ui.button('全部', on_click=self.clear_filter).props('flat dense')
                        for status_key, status_info in STATUS_MAP.items():
                            ui.button(status_info['label'], on_click=lambda s=status_key: self.filter_records('status', s)).props('flat dense')
                    
                    # 搜索框
                    with ui.row().classes('items-center gap-2'):
                        search_input = ui.input('', placeholder='搜索客户、设备或问题描述').props('outlined dense').classes('w-64')
                        ui.button('搜索', on_click=lambda: self.filter_records('search', search_input.value), icon='search').props('flat dense color=primary')
                        ui.button('清除', on_click=self.clear_filter, icon='clear').props('flat dense')
            
            # 记录列表
            ui.label('维修记录列表').classes('text-h6 font-bold')
            self.records_container = ui.column().classes('w-full')
            self.refresh_records()
            
            # 页脚
            with ui.row().classes('w-full justify-center pt-8 border-t'):
                ui.label('维修登记管理系统 v1.0').classes('text-caption text-gray-500')
                ui.label(f'数据更新时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}').classes('text-caption text-gray-500 ml-4')

# 创建应用实例
repair_system = RepairSystem()

# 创建界面
repair_system.create_ui()

# 启动应用
ui.run(title='维修登记管理系统', port=8088, reload=False)
相关推荐
用户7543888677152 小时前
HarmonyOS BLE 快速上手
后端
禅思院2 小时前
在win10上配置 Rust以及修改默认位置问题
开发语言·前端·后端·rust·cargo·mingw64·cargo安装位置
武子康2 小时前
大数据-188 Logstash Output 插件实战:stdout/file/Elasticsearch 输出配置与调优
大数据·后端·logstash
我命由我123452 小时前
Python Flask 开发问题:ImportError: cannot import name ‘escape‘ from ‘flask‘
服务器·开发语言·后端·python·flask·学习方法·python3.11
爱吃烤鸡翅的酸菜鱼3 小时前
Spring Boot 注解全栈指南:涵盖 Bean 注册、配置加载、请求映射、事务控制、数据校验等一网打尽
java·开发语言·spring boot·后端·spring
running up3 小时前
Spring IOC与DI核心注解速查表
java·后端·spring
洛阳泰山3 小时前
快速上手 MaxKB4J:开源企业级 Agentic 工作流系统在 Sealos 上的完整部署指南
java·人工智能·后端
bybitq3 小时前
string,byte,rune,character?详解Golang编码-UTF-8
开发语言·后端·golang
无限进步_3 小时前
【C语言】栈(Stack)数据结构的实现与应用
c语言·开发语言·数据结构·c++·后端·visual studio