
主要功能:
- 新增维修登记 - 客户信息、设备信息和问题描述
- 状态管理 - 待处理、维修中、已完成三种状态
- 记录管理 - 查看、更新状态、删除记录
- 统计概览 - 实时统计各状态数量
- 查询功能 - 按客户、设备或问题描述搜索
数据库结构:
- 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)