代码:
from nicegui import ui, events
import os
import time
import sqlite3
# 创建上传文件保存目录
UPLOAD_DIR = 'uploads'
if not os.path.exists(UPLOAD_DIR):
os.makedirs(UPLOAD_DIR)
class FileUploadApp:
def __init__(self):
self.files = []
self.form_data = {
'name': '',
'email': '',
'phone': '',
'message': ''
}
# 初始化SQLite数据库
self.init_database()
self.create_ui()
def init_database(self):
# 创建数据库连接
self.conn = sqlite3.connect('form_submissions.db', check_same_thread=False)
self.cursor = self.conn.cursor()
# 创建表结构
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS submissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
message TEXT,
file_names TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
self.conn.commit()
def create_ui(self):
# 设置页面标题和样式
ui.page_title('文件上传与表单提交')
ui.label('文件上传与表单提交').classes('text-2xl font-bold mb-4 w-full max-w-2xl mx-auto')
# 创建表单
with ui.card().classes('w-full max-w-2xl mx-auto'):
# 姓名字段
ui.label('姓名').classes('text-lg font-semibold')
self.name_input = ui.input(
placeholder='请输入您的姓名',
on_change=lambda e: self.form_data.update({'name': e.value})
).classes('w-full mb-4').props('dense')
# 邮箱字段
ui.label('邮箱').classes('text-lg font-semibold')
self.email_input = ui.input(
placeholder='请输入您的邮箱',
on_change=lambda e: self.form_data.update({'email': e.value})
).classes('w-full mb-4').props('dense')
# 电话字段
ui.label('电话').classes('text-lg font-semibold')
self.phone_input = ui.input(
placeholder='请输入您的电话',
on_change=lambda e: self.form_data.update({'phone': e.value})
).classes('w-full mb-4').props('dense')
# 留言字段
ui.label('留言').classes('text-lg font-semibold')
self.message_textarea = ui.textarea(
placeholder='请输入您的留言',
on_change=lambda e: self.form_data.update({'message': e.value})
).classes('w-full mb-4').props('dense')
# 文件上传组件
ui.label('文件上传').classes('text-lg font-semibold')
self.upload = ui.upload(
label='选择文件',
on_upload=self.handle_upload,
multiple=True,
max_files=5,
max_file_size=1024 * 1024 * 10, # 10MB
).classes('w-full mb-4')
# 上传文件列表显示
self.file_list = ui.column().classes('mb-4')
# 提交按钮
ui.button(
'提交表单',
on_click=self.handle_submit,
color='primary',
icon='send'
).classes('w-full')
# 结果显示区域
self.result_card = ui.card().classes('w-full max-w-2xl mx-auto mt-4 hidden')
with self.result_card:
self.result_text = ui.label().classes('text-lg')
async def handle_upload(self, event: events.UploadEventArguments):
# 保存上传的文件
timestamp = int(time.time())
file_path = os.path.join(UPLOAD_DIR, f'{timestamp}_{event.file.name}')
with open(file_path, 'wb') as f:
f.write(await event.file.read())
# 添加到文件列表
self.files.append({
'name': event.file.name,
'path': file_path,
'size': event.file.size() # size 是方法,需要调用它
})
# 更新文件列表显示
self.update_file_list()
def update_file_list(self):
# 清空文件列表
self.file_list.clear()
if not self.files:
with self.file_list:
ui.label('暂无上传文件').classes('text-gray-500')
return
# 显示已上传文件
for i, file in enumerate(self.files):
with self.file_list:
with ui.row().classes('items-center justify-between w-full p-2 border-b'):
ui.label(f'{i+1}. {file["name"]} ({self.format_file_size(file["size"])})')
ui.button(
'删除',
on_click=lambda idx=i: self.remove_file(idx),
color='red',
icon='delete'
).classes('text-sm py-1 px-2')
def remove_file(self, index):
# 删除文件
file = self.files.pop(index)
if os.path.exists(file['path']):
os.remove(file['path'])
# 更新文件列表显示
self.update_file_list()
def format_file_size(self, size_bytes):
# 格式化文件大小
# 检查是否是方法对象,如果是则调用它
if callable(size_bytes):
size_bytes = size_bytes()
if size_bytes < 1024:
return f'{size_bytes} B'
elif size_bytes < 1024 * 1024:
return f'{size_bytes / 1024:.2f} KB'
else:
return f'{size_bytes / (1024 * 1024):.2f} MB'
def handle_submit(self):
# 验证表单数据
if not self.form_data['name']:
ui.notify('请输入姓名', color='red')
return
if not self.form_data['email']:
ui.notify('请输入邮箱', color='red')
return
# 检查邮箱格式
if '@' not in self.form_data['email']:
ui.notify('请输入有效的邮箱地址', color='red')
return
# 保存到数据库
file_names = ', '.join([file['name'] for file in self.files])
self.cursor.execute('''
INSERT INTO submissions (name, email, phone, message, file_names)
VALUES (?, ?, ?, ?, ?)
''', (
self.form_data['name'],
self.form_data['email'],
self.form_data['phone'],
self.form_data['message'],
file_names
))
self.conn.commit()
# 显示提交结果
self.result_text.text = f"""
表单提交成功!\n\n
个人信息:\n
姓名:{self.form_data['name']}\n
邮箱:{self.form_data['email']}\n
电话:{self.form_data['phone']}\n
留言:{self.form_data['message']}\n\n
上传文件:{len(self.files)} 个\n
{chr(10).join([f' - {file["name"]}' for file in self.files])}\n\n
数据已保存到数据库!
"""
# 显示结果卡片
self.result_card.classes(remove='hidden')
# 滚动到结果区域
ui.run_javascript('document.querySelector(".nicegui-content").scrollTop = document.body.scrollHeight')
# 清空表单
self.form_data = {
'name': '',
'email': '',
'phone': '',
'message': ''
}
# 重置UI组件
self.name_input.value = ''
self.email_input.value = ''
self.phone_input.value = ''
self.message_textarea.value = ''
# 清空上传文件
self.files = []
self.update_file_list()
# 重置上传组件
self.upload.reset()
# 创建应用实例
app = FileUploadApp()
# 运行应用
ui.run(
title='文件上传与表单提交',
favicon='📁',
port=8080,
reload=True
)
测试数据是否正常:
import sqlite3
# 连接到数据库
conn = sqlite3.connect('form_submissions.db')
cursor = conn.cursor()
# 查看表结构
print("表结构:")
cursor.execute("PRAGMA table_info(submissions)")
table_info = cursor.fetchall()
for column in table_info:
print(f"列名:{column[1]},类型:{column[2]}")
# 查询所有数据
print("\n所有数据:")
cursor.execute("SELECT * FROM submissions")
data = cursor.fetchall()
if not data:
print("数据库中暂无数据")
else:
for row in data:
print(f"ID: {row[0]}")
print(f"姓名: {row[1]}")
print(f"邮箱: {row[2]}")
print(f"电话: {row[3]}")
print(f"留言: {row[4]}")
print(f"文件: {row[5]}")
print(f"提交时间: {row[6]}")
print("-" * 30)
# 关闭连接
cursor.close()
conn.close()
1. 应用简介
这是一个基于 NiceGUI 3.4.0 开发的文件上传与表单提交应用,支持多文件上传、表单验证和数据持久化存储。
2. 功能特点
2.1 核心功能
- 文件上传:支持多文件上传(最多5个文件)
- 文件限制:单个文件最大10MB
- 表单提交:包含姓名、邮箱、电话和留言字段
- 表单验证:姓名和邮箱为必填项,邮箱格式验证
- 数据存储:使用SQLite3持久化存储表单数据
- 界面反馈:提交成功后显示结果信息
- 自动重置:提交后自动清空表单和文件列表
2.2 用户体验
- 响应式设计:适配不同屏幕尺寸
- 实时反馈:上传进度和错误提示
- 直观操作:简洁明了的用户界面
3. 技术栈
- 后端框架:Python 3.13
- 前端框架:NiceGUI 3.4.0
- 数据库:SQLite3
- 文件存储:本地文件系统
4. 安装和运行
4.1 环境要求
- Python 3.10+
- pip 包管理工具
4.2 安装依赖
pip install nicegui
4.3 运行应用
python up01.py
应用将在以下地址运行:
- http://localhost:8080
- 局域网内可通过本机IP访问
5. 使用指南
5.1 上传文件
- 点击"选择文件"按钮
- 从本地选择要上传的文件(最多5个)
- 文件将自动上传并显示在文件列表中
- 可以点击"删除"按钮移除不需要的文件
5.2 填写表单
- 姓名:必填项,输入您的姓名
- 邮箱:必填项,输入有效的邮箱地址
- 电话:选填项,输入您的联系电话
- 留言:选填项,输入您的留言内容
5.3 提交表单
- 确认所有必填项已填写
- 点击"提交表单"按钮
- 系统将验证表单数据
- 验证通过后,数据将保存到数据库
- 提交成功后显示结果信息
- 表单和文件列表将自动重置
6. 数据库说明
6.1 数据库文件
- 文件名:
form_submissions.db - 位置:应用程序同级目录
6.2 表结构
CREATE TABLE submissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
message TEXT,
file_names TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
6.3 字段说明
- id:自动递增的主键
- name:用户姓名
- email:用户邮箱
- phone:用户电话
- message:用户留言
- file_names:上传的文件名列表(逗号分隔)
- created_at:提交时间(自动生成)
6.4 数据查询
可以使用 test_db.py 脚本查看数据库内容:
python test_db.py
7. 代码结构
7.1 主要类和方法
FileUploadApp 类
__init__():初始化应用init_database():初始化数据库连接和表结构create_ui():创建用户界面handle_upload():处理文件上传update_file_list():更新文件列表显示remove_file():删除上传的文件format_file_size():格式化文件大小显示handle_submit():处理表单提交
7.2 目录结构
├── up01.py # 主应用文件
├── up01_README.md # 应用说明文档
├── form_submissions.db # SQLite数据库文件
├── uploads/ # 上传文件存储目录
└── test_db.py # 数据库测试脚本
8. 常见问题
8.1 上传文件失败
- 检查文件大小是否超过10MB
- 检查文件数量是否超过5个
- 检查网络连接是否正常
8.2 表单提交失败
- 确认姓名和邮箱已填写
- 确认邮箱格式正确(包含@符号)
8.3 数据库连接错误
- 确保应用程序有写入权限
- 检查是否有其他进程占用数据库文件
9. 技术细节
9.1 文件上传机制
- 使用 NiceGUI 的
ui.upload组件 - 支持异步文件读取
- 文件保存到
uploads目录,使用时间戳+原文件名命名
9.2 数据持久化
- 使用 SQLite3 数据库存储表单数据
- 支持多线程访问(
check_same_thread=False) - 参数化查询防止SQL注入
9.3 UI设计
-
使用 NiceGUI 的
with上下文管理器嵌套组件 -
响应式布局,自适应不同屏幕尺寸
-
组件样式通过
.classes()方法设置 -
实现基本文件上传功能
-
实现表单提交和验证
-
集成SQLite3数据存储
简化代码:
from nicegui import ui, events
import os
import sqlite3
UPLOAD_DIR = 'uploads'
os.makedirs(UPLOAD_DIR, exist_ok=True)
class FileUploadApp:
def __init__(self):
self.files = []
self.init_database()
self.create_ui()
def init_database(self):
self.conn = sqlite3.connect('form_submissions.db', check_same_thread=False)
self.cursor = self.conn.cursor()
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS submissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT,
phone TEXT,
message TEXT,
file_names TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
self.conn.commit()
def create_ui(self):
ui.page_title('文件上传与表单提交')
ui.label('文件上传与表单提交').classes('text-2xl font-bold mb-4 w-full max-w-2xl mx-auto')
with ui.card().classes('w-full max-w-2xl mx-auto'):
# 表单字段
self.name = ui.input('姓名', placeholder='请输入您的姓名').classes('w-full mb-4')
self.email = ui.input('邮箱', placeholder='请输入您的邮箱').classes('w-full mb-4')
self.phone = ui.input('电话', placeholder='请输入您的电话').classes('w-full mb-4')
self.message = ui.textarea('留言', placeholder='请输入您的留言').classes('w-full mb-4')
# 文件上传
ui.label('文件上传').classes('text-lg font-semibold')
self.upload = ui.upload(
label='选择文件',
on_upload=self.handle_upload,
multiple=True,
max_files=5
).classes('w-full mb-4')
self.file_list = ui.column().classes('mb-4')
# 提交按钮
ui.button('提交表单', on_click=self.submit, color='primary').classes('w-full')
self.result = ui.label().classes('w-full max-w-2xl mx-auto mt-4 hidden')
async def handle_upload(self, e: events.UploadEventArguments):
file_path = os.path.join(UPLOAD_DIR, e.file.name)
with open(file_path, 'wb') as f:
f.write(await e.file.read())
self.files.append({
'name': e.file.name,
'path': file_path
})
self.update_file_list()
def update_file_list(self):
self.file_list.clear()
if not self.files:
with self.file_list:
ui.label('暂无上传文件').classes('text-gray-500')
return
for i, file in enumerate(self.files):
with self.file_list:
with ui.row().classes('items-center justify-between w-full p-2 border-b'):
ui.label(f'{i+1}. {file["name"]}')
ui.button('删除', on_click=lambda idx=i: self.remove_file(idx), color='red', icon='delete')
def remove_file(self, index):
file = self.files.pop(index)
if os.path.exists(file['path']):
os.remove(file['path'])
self.update_file_list()
def submit(self):
# 简单验证
if not self.name.value or not self.email.value or '@' not in self.email.value:
ui.notify('请填写正确的姓名和邮箱', color='red')
return
# 保存到数据库
file_names = ', '.join([f['name'] for f in self.files])
self.cursor.execute('''
INSERT INTO submissions (name, email, phone, message, file_names)
VALUES (?, ?, ?, ?, ?)
''', (self.name.value, self.email.value, self.phone.value, self.message.value, file_names))
self.conn.commit()
# 显示结果
self.result.text = f"""
提交成功!
姓名:{self.name.value}
邮箱:{self.email.value}
文件:{file_names or '无'}
"""
self.result.classes(remove='hidden')
# 清空表单
self.name.value = self.email.value = self.phone.value = self.message.value = ''
self.files.clear()
self.upload.reset()
self.update_file_list()
app = FileUploadApp()
ui.run(title='文件上传与表单提交', port=8080)