基础文件上传例子
from nicegui import ui, events
import os
import aiofiles
# 创建上传目录
UPLOAD_DIR = "uploaded_files"
os.makedirs(UPLOAD_DIR, exist_ok=True)
# 基础文件上传处理器
async def handle_upload(e: events.UploadEventArguments):
"""处理上传的文件"""
file = e.file # 获取文件对象
# 获取文件信息
filename = file.name
content_type = file.content_type
file_size = file.size()
ui.notify(f'📤 正在上传: {filename} ({file_size} bytes)')
# 保存文件
save_path = os.path.join(UPLOAD_DIR, filename)
try:
# 使用异步方式保存文件
await file.save(save_path)
ui.notify(f'✅ 文件 {filename} 上传成功!', color='positive')
# 显示文件信息
update_file_info(filename, content_type, file_size, save_path)
except Exception as ex:
ui.notify(f'❌ 上传失败: {str(ex)}', color='negative')
def update_file_info(filename, content_type, size, path):
"""更新文件信息显示"""
info_container.clear()
with info_container:
ui.label('📋 文件信息').classes('text-lg font-bold mb-2')
with ui.card().classes('w-full'):
ui.label(f'文件名: {filename}')
ui.label(f'类型: {content_type}')
ui.label(f'大小: {size} bytes ({size/1024:.2f} KB)')
ui.label(f'保存路径: {path}')
# 创建界面
ui.label('📁 NiceGUI 文件上传示例').classes('text-2xl font-bold mb-4')
# 文件信息容器
info_container = ui.column().classes('w-full mb-4')
# 基础上传组件
ui.upload(
on_upload=handle_upload,
label='选择文件或拖拽到此处',
multiple=False,
auto_upload=True, # 选择文件后自动上传
).classes('max-w-full')
ui.run(title='文件上传示例')
增强版:多文件上传与类型处理
from nicegui import ui, events
import os
import json
import pandas as pd
import io
from pathlib import Path
# 上传配置
UPLOAD_DIR = Path("uploaded_files")
UPLOAD_DIR.mkdir(exist_ok=True)
# 文件处理函数
async def handle_file_upload(e: events.UploadEventArguments):
"""增强版文件上传处理器"""
file = e.file
# 显示基本信息
info_area.clear()
with info_area:
ui.label('📋 文件信息').classes('text-xl font-bold mb-3')
with ui.card().classes('w-full mb-4'):
ui.label(f'文件名: {file.name}').classes('font-bold')
ui.label(f'类型: {file.content_type or "未知"}')
ui.label(f'大小: {file.size()} 字节 ({file.size()/1024/1024:.2f} MB)')
# 根据文件类型处理内容
await process_file_by_type(file)
async def process_file_by_type(file):
"""根据文件类型处理内容"""
content_type = file.content_type or ''
try:
if content_type == 'application/json':
await handle_json_file(file)
elif content_type.startswith('text/'):
await handle_text_file(file)
elif content_type.startswith('image/'):
await handle_image_file(file)
elif content_type == 'text/csv' or file.name.endswith('.csv'):
await handle_csv_file(file)
else:
await handle_binary_file(file)
except Exception as ex:
ui.notify(f'处理文件时出错: {str(ex)}', color='negative')
async def handle_json_file(file):
"""处理JSON文件"""
with info_area:
ui.label('🎯 JSON文件内容:').classes('text-lg font-bold mb-2')
try:
json_data = await file.json()
ui.json(json_data)
# 保存文件
save_path = UPLOAD_DIR / file.name
await file.save(save_path)
ui.notify('JSON文件保存成功!', color='positive')
except Exception as ex:
ui.notify(f'JSON解析失败: {str(ex)}', color='negative')
async def handle_text_file(file):
"""处理文本文件"""
with info_area:
ui.label('📝 文本文件内容:').classes('text-lg font-bold mb-2')
try:
text_content = await file.text()
# 显示内容预览
with ui.card().classes('w-full'):
ui.label('内容预览:').classes('font-bold mb-2')
preview_text = text_content[:1000] + '...' if len(text_content) > 1000 else text_content
ui.label(preview_text).classes('font-mono text-sm')
# 保存文件
save_path = UPLOAD_DIR / file.name
await file.save(save_path)
ui.notify('文本文件保存成功!', color='positive')
except Exception as ex:
ui.notify(f'文本读取失败: {str(ex)}', color='negative')
async def handle_csv_file(file):
"""处理CSV文件"""
with info_area:
ui.label('📊 CSV文件内容:').classes('text-lg font-bold mb-2')
try:
csv_content = await file.text()
# 使用pandas读取CSV
df = pd.read_csv(io.StringIO(csv_content))
ui.label(f'数据概览 - 共 {len(df)} 行,{len(df.columns)} 列')
# 显示前几行数据
with ui.card().classes('w-full'):
ui.label('前5行数据:').classes('font-bold mb-2')
ui.html(df.head().to_html(classes='table-auto'))
# 保存文件
save_path = UPLOAD_DIR / file.name
await file.save(save_path)
ui.notify('CSV文件保存成功!', color='positive')
except Exception as ex:
ui.notify(f'CSV处理失败: {str(ex)}', color='negative')
async def handle_image_file(file):
"""处理图片文件"""
with info_area:
ui.label('🖼️ 图片文件').classes('text-lg font-bold mb-2')
# 保存图片文件
save_path = UPLOAD_DIR / file.name
await file.save(save_path)
# 显示图片
ui.image(str(save_path)).classes('w-full max-w-md')
ui.notify('图片文件保存成功!', color='positive')
async def handle_binary_file(file):
"""处理二进制文件"""
with info_area:
ui.label('📁 二进制文件').classes('text-lg font-bold mb-2')
# 保存文件
save_path = UPLOAD_DIR / file.name
await file.save(save_path)
ui.label(f'文件已保存到: {save_path}')
ui.notify('二进制文件保存成功!', color='positive')
def handle_rejected():
"""处理被拒绝的文件"""
ui.notify('文件上传被拒绝(可能超过大小限制)', color='negative')
def handle_multi_upload(e: events.MultiUploadEventArguments):
"""处理多文件上传"""
ui.notify(f'📤 准备上传 {len(e.files)} 个文件...')
for file in e.files:
ui.notify(f'正在处理: {file.name}')
# 创建界面
ui.label('🚀 增强版文件上传系统').classes('text-2xl font-bold mb-4')
# 文件信息展示区域
info_area = ui.column().classes('w-full mb-6')
# 增强版上传组件
with ui.card().classes('w-full mb-6'):
ui.label('📤 文件上传').classes('text-lg font-bold mb-3')
ui.upload(
on_upload=handle_file_upload,
on_multi_upload=handle_multi_upload,
on_rejected=handle_rejected,
multiple=True, # 允许多文件上传
auto_upload=False, # 手动点击上传
max_file_size=50_000_000, # 50MB 限制
max_files=10, # 最多10个文件
label='选择文件或拖拽到此处',
).classes('max-w-full').props('accept=*')
# 文件类型说明
with ui.expansion('📖 支持的文件类型').classes('w-full'):
ui.markdown('''
### 支持的文件类型:
- **JSON文件** (.json): 自动解析并显示结构
- **文本文件** (.txt, .md, .py等): 显示内容预览
- **CSV文件** (.csv): 使用pandas显示数据表格
- **图片文件** (.jpg, .png, .gif等): 显示图片预览
- **其他文件**: 保存为二进制文件
### 限制:
- 单个文件最大 50MB
- 最多同时上传 10 个文件
''')
ui.run(title='增强版文件上传系统')
实用版:带文件管理的上传系统
from nicegui import ui, events
import os
import shutil
from datetime import datetime
from pathlib import Path
import asyncio
from starlette.responses import FileResponse
class FileManager:
def __init__(self):
self.upload_dir = Path("uploaded_files")
self.upload_dir.mkdir(exist_ok=True)
self.setup_ui()
self.refresh_file_list()
self.setup_download_route()
def setup_download_route(self):
"""设置下载路由"""
from nicegui import app
@app.get('/download/{filename}')
async def download_file_route(filename: str):
file_path = self.upload_dir / filename
if file_path.exists() and file_path.is_file():
return FileResponse(
path=str(file_path),
filename=filename,
media_type='application/octet-stream'
)
else:
return {"error": "文件不存在"}
def setup_ui(self):
"""设置界面"""
ui.label('📁 文件管理系统').classes('text-2xl font-bold mb-4')
# 上传区域
with ui.card().classes('w-full mb-6'):
ui.label('📤 文件上传').classes('text-lg font-bold mb-3')
self.upload_component = ui.upload(
on_upload=self.handle_upload,
on_rejected=self.handle_rejected,
multiple=True,
auto_upload=True,
max_file_size=100_000_000, # 100MB
).classes('max-w-full')
# 上传统计
self.upload_stats = ui.label('准备就绪').classes('text-sm text-gray-600 mt-2')
# 文件列表区域
with ui.card().classes('w-full'):
ui.label('📋 文件列表').classes('text-lg font-bold mb-3')
# 操作按钮
with ui.row().classes('mb-3'):
ui.button('🔄 刷新', on_click=self.refresh_file_list).props('size=sm')
ui.button('🗑️ 清空所有', on_click=self.clear_all_files).props('size=sm color=negative')
self.total_size_label = ui.label('').classes('ml-auto')
# 文件列表容器
self.file_list_container = ui.column().classes('w-full')
async def handle_upload(self, e: events.UploadEventArguments):
"""处理文件上传"""
file = e.file
# 生成唯一文件名(避免重名)
original_name = file.name
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
base_name = Path(original_name).stem
extension = Path(original_name).suffix
unique_name = f"{base_name}_{timestamp}{extension}"
save_path = self.upload_dir / unique_name
try:
# 保存文件
await file.save(save_path)
# 更新统计
file_size = file.size()
self.upload_stats.text = f'✅ 已上传: {original_name} ({file_size} bytes)'
# 刷新文件列表
self.refresh_file_list()
ui.notify(f'文件 {original_name} 上传成功!', color='positive')
except Exception as ex:
ui.notify(f'上传失败: {str(ex)}', color='negative')
def handle_rejected(self):
"""处理被拒绝的文件"""
ui.notify('文件上传被拒绝(可能超过大小限制)', color='negative')
def refresh_file_list(self):
"""刷新文件列表"""
self.file_list_container.clear()
files = []
total_size = 0
# 获取所有文件
for file_path in self.upload_dir.iterdir():
if file_path.is_file():
stat = file_path.stat()
files.append({
'name': file_path.name,
'size': stat.st_size,
'modified': datetime.fromtimestamp(stat.st_mtime),
'path': file_path
})
total_size += stat.st_size
# 更新总大小显示
self.total_size_label.text = f'总大小: {total_size / 1024 / 1024:.2f} MB'
if not files:
with self.file_list_container:
ui.label('暂无文件').classes('text-gray-500 text-center py-8')
return
# 按修改时间排序
files.sort(key=lambda x: x['modified'], reverse=True)
# 显示文件列表
with self.file_list_container:
for file_info in files:
self.create_file_item(file_info)
def create_file_item(self, file_info):
"""创建文件项"""
with ui.card().classes('w-full mb-2'):
with ui.row().classes('items-center justify-between w-full'):
# 文件信息
with ui.row().classes('items-center'):
# 文件图标
ui.icon('insert_drive_file').classes('text-blue-500 mr-2')
# 文件名称和大小
with ui.column().classes('mr-4'):
ui.label(file_info['name']).classes('font-bold')
size_mb = file_info['size'] / 1024 / 1024
ui.label(f'{size_mb:.2f} MB').classes('text-sm text-gray-600')
# 修改时间
ui.label(file_info['modified'].strftime('%Y-%m-%d %H:%M')).classes(
'text-sm text-gray-500'
)
# 操作按钮
with ui.row().classes('gap-1'):
ui.button(
'',
icon='download',
on_click=lambda p=file_info['path']: self.download_file(p)
).props('size=sm flat color=primary')
ui.button(
'',
icon='delete',
on_click=lambda p=file_info['path']: self.delete_file(p)
).props('size=sm flat color=negative')
def download_file(self, file_path: Path):
"""下载文件"""
try:
# 使用JavaScript触发下载
filename = file_path.name
download_url = f'/download/{filename}'
ui.run_javascript(f'''
const link = document.createElement('a');
link.href = '{download_url}';
link.download = '{filename}';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
''')
except Exception as ex:
ui.notify(f'下载失败: {str(ex)}', color='negative')
def delete_file(self, file_path: Path):
"""删除文件"""
def confirm_delete():
try:
file_path.unlink() # 删除文件
self.refresh_file_list()
ui.notify(f'文件 {file_path.name} 已删除', color='positive')
except Exception as ex:
ui.notify(f'删除失败: {str(ex)}', color='negative')
# 确认对话框
with ui.dialog() as dialog, ui.card():
ui.label(f'确定要删除文件 "{file_path.name}" 吗?')
with ui.row():
ui.button('确定', on_click=lambda: [confirm_delete(), dialog.close()])
ui.button('取消', on_click=dialog.close)
dialog.open()
def clear_all_files(self):
"""清空所有文件"""
def confirm_clear():
try:
# 删除upload目录下的所有文件
for file_path in self.upload_dir.iterdir():
if file_path.is_file():
file_path.unlink()
self.refresh_file_list()
ui.notify('所有文件已清空', color='positive')
except Exception as ex:
ui.notify(f'清空失败: {str(ex)}', color='negative')
# 确认对话框
with ui.dialog() as dialog, ui.card():
ui.label('确定要清空所有文件吗?此操作不可恢复。')
with ui.row():
ui.button('确定', on_click=lambda: [confirm_clear(), dialog.close()])
ui.button('取消', on_click=dialog.close)
dialog.open()
# 创建应用
FileManager()
ui.run(title='文件管理系统')
关键要点说明
-
基础用法:
ui.upload(on_upload=handle_upload, auto_upload=True) -
文件事件处理:
async def handle_upload(e: events.UploadEventArguments): file = e.file # 获取文件对象 filename = file.name content = await file.text() # 或 read(), json(), save(path) -
多文件上传:
ui.upload( on_upload=single_file_handler, on_multi_upload=multi_file_handler, multiple=True ) -
文件限制:
ui.upload( max_file_size=10_000_000, # 10MB max_files=5, # 最多5个文件 accepted_file_types='.json,.txt,.csv' # 接受的文件类型 )
这些例子展示了 NiceGUI 3.4.0 中 ui.upload 的完整用法,包括基础上传、文件类型处理、多文件上传和文件管理等功能。