工作原理与交互流程

元素和控件

在 NiceGUI中:

  • 元素 (Elements) 是构成用户界面 (UI) 的基本构建块
  • 控件 (Controls) 是元素的一种,特指那些用户可以直接与之交互的元素。

官方提供了非常多的元素和控件,包含各种使用场景,这一部分直接在官方文档中查找并选择我们需要的即可。

布局

with会把后面的..作为一个容器:

最基础的布局工具

默认情况下,我们创建的每个元素都会像写文章一样,从上到下一个接一个地排列。ui.row 和 ui.column 可以改变这个默认行为。

  • ui.column: 元素在容器内从上到下垂直排列(这是默认行为,但显式使用 ui.column 可以更好地控制一组元素)。
  • ui.row: 元素在容器内从左到右水平排列。

Important

在 NiceGUI 中,我们使用 Python 的 with 语句来定义一个容器的范围。所有在 with 代码块内创建的元素,都会被自动放入这个容器中(容器中的元素可以是任意的)。

下面是一个简单的例子:

from nicegui import ui  
  
with ui.row():  
    with ui.column():  
        ui.button('1')  
        ui.button('2')  
  
    with ui.column():  
        ui.button('3')  
        ui.button('4')  
ui.run()

最简单的布局示例

常用的几个布局工具

官方文档中有非常多的布局工具,我们只要知道最常见的几个就可以,其他的要用到时候查一查文档。

  • ui.card: 它本质上是一个带有边框、阴影和内边距的容器,能让界面更有层次感。

绑定属性

NiceGUI 能够直接将 UI 元素绑定到模型(这里的模型必须是对象或者字典,不能是普通变量)。每个元素都提供类似bind_valuebind_visibility的方法,用于与相应的属性创建双向绑定。要定义单向绑定,请使用这些方法的_from_to变体。

from nicegui import ui
 
class Demo:
    def __init__(self):
        self.number = 1
 
demo = Demo()
v = ui.checkbox('visible', value=True)
with ui.column().bind_visibility_from(v, 'value'):
    ui.slider(min=1, max=3).bind_value(demo, 'number')
    ui.toggle({1: 'A', 2: 'B', 3: 'C'}).bind_value(demo, 'number')
    ui.number().bind_value(demo, 'number')
 
ui.run()

绑定到变量

如果需要绑定到变量,我们可以先用globals()获取一个包含所有全局变量的字典,然后再绑定。

from nicegui import ui  
  
date = '2025-09-26'  
print(globals())  
ui.label().bind_text_from(globals(), 'date')  
  
ui.run()

转换函数

  • backward: 当数据从数据模型流向 UI元素 时,backward函数会被调用。它接收来自模型的值,并返回一个处理过的新值以更新UI元素。
  • forward: 当数据从 UI元素 流向数据模型 时,forward函数会被调用。它接收来自UI元素的值,并返回一个处理过的新值以更新数据模型。
from nicegui import ui  
  
i = ui.input(value='Lorem ipsum')  
ui.label().bind_text_from(i, 'value',  
                          backward=lambda text: f'{len(text)} characters')  
  
ui.run()
from nicegui import ui  
  
data = {'text': 'Hello'}  
  
ui.input(label='Enter some text').bind_value_to(  
    data, 'text',  
    forward=lambda text: text.upper()  
)  
  
  
ui.label().bind_text_from(data, 'text', backward=lambda text: f"Model value is: {text}")  
  
ui.run()

绑定到存储

绑定也适用于app.storage

行动与事件

预定义事件

对于预定义事件,比如说on_click等,我们直接给它传入一个可调用对象

Error

一个关键的错误观念要避免:
你传递给 on_click 的不是函数执行后的结果,而是函数本身。

看一个例子:

from nicegui import ui  
  
def show_message():  
    ui.notify('你好!')  
  
# 这会立刻执行show_message()函数,显示通知,然后把函数的返回值 None 传给 on_click
# 按钮点击时什么都不会发生,因为它的“任务”是 None
ui.button('错误的按钮', on_click=show_message())  
  
# 这里我们传递的是 show_message 这个函数对象本身  
# 按钮收到了这个“任务”,并会在被点击时去执行它  
ui.button('正确的按钮', on_click=show_message)  
  
ui.run()

如果你的任务非常简单,只有一行代码,我们更应该使用lambda表达式

from nicegui import ui  
  
# 直接在 on_click 参数里定义一个临时的、匿名的任务  
ui.button('hello', on_click=lambda: ui.notify('你好!'))  
  
ui.run()

这里的 lambda: ui.notify(...) 就创建了一个临时的、没有名字的可调用对象。这个函数对象本身被赋值给了 on_click 参数。

通用事件

NiceGUI 为最常见的事件提供了方便的快捷方式,比如 ui.button 的 on_click 参数和 ui.input 的 on_change 参数。这些是“专用接口”,简单直接。 但是,前端世界(浏览器中的 JavaScript)有成百上千种不同的事件(比如鼠标移动、键盘按下、元素获得焦点、滚动、拖拽等等)。NiceGUI 的开发者不可能为每一种事件都创建一个专门的 on_... 参数。.on() 方法就是为了解决这个问题而生的。它允许你监听任何前端支持的事件,并将这个事件与后端的 Python 函数连接起来。

Example

例子: 文档中的 mousemove (鼠标移动)。

# ui.button 没有 on_mousemove 这个参数
# 所以我们必须使用 .on() 来监听鼠标移动事件

ui.button(‘C’).on(‘mousemove’, lambda: ui.notify(‘You moved on button C.‘))

我们初学者只需要了解这么多即可,大多数时候我们用预定义事件就够了。

异步事件

在处理耗时任务时,使用异步事件不会冻结UI,整个界面可以保持完全可交互

Tip

黄金法则:只要你的事件处理函数中包含任何可能耗时的I/O操作,就应该使用异步。 以下是典型的应该使用异步事件的场景:

  1. 网络请求
  2. 数据库操作
  3. 文件读写
  4. 运行子进程执行外部命令并等待其完成

这个版本展示了在等待时,另一个按钮依然可以点击:

import asyncio  
from nicegui import ui  
  
async def long_task():  
    ui.notify('开始异步等待...')  
    await asyncio.sleep(5)  
    ui.notify('异步等待结束.')  
  
ui.button('启动异步任务 (5s)', on_click=long_task)  
ui.button('点我测试响应', on_click=lambda: ui.notify('UI依然响应!'))  
  
ui.run()

这个版本UI会被冻结,必须等同步等待结束后,另一个按钮才能被点击:

import time  
from nicegui import ui  
  
def long_task():  
    ui.notify('开始同步等待...')  
    time.sleep(5)  # 阻塞操作  
    ui.notify('同步等待结束.')  
  
ui.button('启动同步任务 (5s)', on_click=long_task)  
ui.button('点我测试响应', on_click=lambda: ui.notify('UI依然响应!'))  
  
ui.run()

计时器

NiceGUI 诞生的一个主要驱动力是需要一种简单的方法来定期更新界面,例如显示包含传入测量值的图表。计时器会以给定的间隔重复执行回调。 参数:

  • interval: 调用计时器的间隔(可以在运行时更改)
  • callback: 间隔结束后执行的函数或协程
  • active: 是否应该执行回调(可以在运行时更改)
  • once: 回调是否仅在间隔指定的延迟后执行一次(默认值:False)
  • immediate: 是否应立即执行回调(默认值:True,如果once为True则忽略,在版本 2.9.0 中添加
from datetime import datetime
from nicegui import ui
 
label = ui.label()
ui.timer(1.0, lambda: label.set_text(f'{datetime.now():%X}'))
 
ui.run()

使用timer.cancel取消计时器后,它将无法再被激活。

from nicegui import ui
 
slider = ui.slider(min=0, max=1, value=0.5)
timer = ui.timer(0.1, lambda: slider.set_value((slider.value + 0.01) % 1.0))
ui.switch('active').bind_value_to(timer, 'active')
ui.button('Cancel', on_click=timer.cancel)
 
ui.run()

页面和路由

私有页面(@ui.page装饰器)

基本语法

from nicegui import ui  
  
@ui.page('/other_page', title='other_page')  
def other_page():  
    ui.label('Welcome to the other side')  
  
@ui.page('/dark_page', dark=True)  
def dark_page():  
    ui.label('Welcome to the dark side')  
  
ui.link('Visit other page', other_page)  
ui.link('Visit dark page', dark_page)  
  
ui.run()
  • 创建方式: 使用 @ui.page('/some_path') 装饰器来标记一个函数。
  • 核心特性每个用户(或每个浏览器标签页)访问时,都会独立执行一次这个函数,创建一个全新的、隔离的页面实例。
  • 适用场景: 需要为每个用户提供独立状态的页面,比如用户个人资料页、购物车等。

每次访问都会发现页面是不同的uuid:

from nicegui import ui
from uuid import uuid4
 
@ui.page('/private_page')
async def private_page():
    ui.label(f'private page with ID {uuid4()}')
 
ui.label(f'shared auto-index page with ID {uuid4()}')
ui.link('private page', private_page)
 
ui.run()

完整参数列表

  • path: 定义这个新页面的 URL 路由。这个值必须以斜杠 / 开头。例如,path='/products' 会让这个页面在用户访问 http://your-server/products 时显示。
  • title: (可选) 设置浏览器标签页上显示的标题。
  • viewport: (可选) 设置页面 <meta name="viewport" ...> 标签的内容,用于控制移动设备上的布局和缩放。
  • favicon: (可选) 设置浏览器标签页上的小图标。可以是一个相对文件路径或一个绝对 URL。如果未提供,则使用 NiceGUI 默认图标。
  • dark: 控制此页面是否使用 Quasar 的暗黑模式。如果未设置,则遵循 ui.run() 命令的全局 dark 参数。
  • language: 设置页面的语言(例如 'zh-CN')。如果未设置,则遵循 ui.run() 命令的全局 language 参数。
  • response_timeout: 被装饰的页面构建函数允许执行的最长时间,默认为 3.0 秒。如果超时,将返回错误。
  • reconnect_timeout: 服务器等待断开连接的浏览器重新连接的最长时间。如果未设置,则遵循 ui.run() 命令的全局 reconnect_timeout 参数。
  • api_router: (高级用法) 指定一个自定义的 FastAPI APIRouter 实例来注册此页面路由,默认为 None 并使用默认路由器。
  • kwargs: (高级用法) 传递额外的关键字参数给底层的 FastAPI 的 @app.get() 装饰器,用于 API 文档生成等高级配置。

共享的自动索引页

  • 创建方式任何没有被 @ui.page 装饰器包裹的 UI 元素,都会被自动放置在根路径 / 的一个页面上。
  • 核心特性: 这个页面在服务器启动时只创建一次,所有连接到该服务器的用户看到的都是同一个页面实例。一个用户的操作会实时反映在所有其他用户的屏幕上。
  • 适用场景: 需要数据共享和实时协作的仪表盘(Dashboard)、聊天室、公共展示页面等。

自动索引页面上显示的 ID 在浏览器重新加载页面时保持不变: 代码示例

页面布局

你可以在任何页面(私有或共享)中添加复杂的布局元素,这些元素来自于 Quasar 框架:

  • ui.header: 在页面顶部创建固定的或可滚动的页眉。
  • ui.footer: 在页面底部创建页脚。
  • ui.left_drawer / ui.right_drawer: 创建左侧或右侧的抽屉式导航栏或菜单。
  • ui.page_sticky: 将元素“粘”在屏幕的特定位置。

sub_pages

注意:这是一个实验性功能,API 可能会发生变化。

什么是单页应用 (SPA)? 传统的网站,你每点击一个链接(比如从 / 到 /other),浏览器都会向服务器发送一个全新的请求,然后服务器返回一个完整的 HTML 页面,浏览器会整个刷新来显示新内容。 而在单页应用中,当你点击链接时,页面不会整体刷新。相反,只有页面中需要变化的部分内容会被动态地替换掉。这通常是通过 JavaScript 在前端实现的,给用户的感觉是应用响应更快、更流畅,就像一个桌面应用。

NiceGUI 的 ui.sub_pages 就是用来实现这个效果的工具。

Question

这里等之后功能成熟了再来了解。

参数注入

得益于底层的 FastAPI,页面函数可以直接从 URL 中接收参数

路径参数

from nicegui import ui  
  
User = {  
    "name": "",  
}  
  
def handle_greet():  
    ui.navigate.to(f'/greet/{User["name"]}')  
  
# 1. 定义一个页面,URL路径中的一部分 '{name}' 将作为参数  
@ui.page('/greet/{name}')  
def greet_page(name: str):  
    # 2. 函数的参数 'name' 会自动接收来自 URL 的值  
    ui.label(f'Hello, {name}!').classes('text-h4')  
  
  
ui.input().bind_value_to(User, 'name')  
ui.button(text='greet', on_click=handle_greet)  
  
ui.run()

查询参数

from nicegui import ui  
  
# 1. 定义一个固定路径的页面  
@ui.page('/add')  
def add_page(a: int, b: int):  
    # 2. 函数参数 'a' 和 'b' 会自动从查询字符串中获取  
    result = a + b  
    ui.label(f'{a} + {b} = {result}').classes('text-h4')  
  
ui.run()

浏览器与交互控制

  • 动态修改页面标题: 使用 ui.page_title('新标题') 可以在事件处理中随时改变当前页面的标题。
  • 浏览器历史导航 (ui.navigate):提供了一组函数来控制浏览器的行为:
    • ui.navigate.back(): 后退
    • ui.navigate.forward(): 前进
    • ui.navigate.reload(): 刷新
    • ui.navigate.to('URL'):  可以将用户导航到任意内部或外部链接。
  • 触发文件下载 (ui.download): 提供了一组函数来让用户的浏览器下载文件:
    • ui.download.file('local_path.txt'): 下载服务器上的本地文件。
    • ui.download.from_url('/logo.png'): 下载服务器上已注册的静态资源。
    • ui.download.content('内容', '文件名.txt'): 将内存中的字符串或字节作为文件下载。

静态资源与文件服务

  • 提供静态文件 (app.add_static_files):
    • 可以将服务器上的一个本地文件夹(如 images)映射到一个 URL 路径(如 /static)。
    • 这使得浏览器可以通过 /static/my_image.png 这样的 URL 访问到服务器上的文件,主要用于图片、CSS、JS 等文件。
  • 提供媒体文件 (app.add_media_files):
    • 与静态文件类似,但专门为音视频优化
    • 它支持流式传输 (Streaming),允许浏览器进行快进、拖动进度条等操作,这是 add_static_files 做不到的。

将HTML添加到页面

你可以通过调用ui.add_head_html or ui.add_body_html将HTML代码注入到页面,这对于添加自定义 CSS 样式或 JavaScript 代码非常有用。

from nicegui import ui
 
ui.add_head_html('''
    <style>
        .my-red-label {
            color: Crimson;
            font-weight: bold;
        }
    </style>
''')
ui.label('RED').classes('my-red-label')
 
ui.run()

编写API

NiceGUI 基于FastAPI。这意味着你可以使用 FastAPI 的所有功能。例如,除了图形用户界面之外,还可以实现 RESTful API。只需要从nicegui导入app对象即可。 你还可以在页面函数中返回任何其他 FastAPI 响应对象。例如,您可以返回一个,RedirectResponse以便在满足某些条件时将用户重定向到另一个页面。

from nicegui import app, ui  
  
# 使用 @app.get 装饰器来定义一个 API 路由, 这和 FastAPI 的用法完全一样  
@app.get('/api/hello')  
def hello_api():  
    # 返回一个 Python 字典, FastAPI 会自动将其转换为 JSON 格式的响应  
    return {"message": "Hello from the API!"}  
  
ui.run()

样式控制

在NiceGUI中,最简单的样式定义方式就是使用Tailwind CSS。Tailwind是一个“功能类优先”的CSS框架,你不需要写CSS代码,而是通过组合预设的类名来构建样式。NiceGUI对其提供了卓越的支持。

在 NiceGUI 中,你有两种主要方式来使用 Tailwind工具类:

  1. .classes:一次性传入一个包含多个类的字符串,这是最接近原生 Tailwind HTML 写法的方式,非常适合直接从 Tailwind 官方文档或其他示例中复制代码。我们后面所有的示例都将只使用 .classes() 方法。
ui.label('Hello').classes('text-blue-500 font-bold')
  1. ** .tailwind** :链式调用,非常 Pythonic。
ui.label('Hello').tailwind.text_color('blue-400').font_size('5xl')

安装和部署

服务器托管

打包成windows应用

ios/安卓(PWA)

渐进式web应用,从浏览器直接“安装到桌面”。

封装组件

用函数封装

用类封装

将组件拆分到不同文件 (适用于大型项目)