FastAPI与NiceGUI集成示例

44 阅读5分钟

图:

image.png

官方例 代码:

#!/usr/bin/env python3
import uvicorn
from fastapi import FastAPI

from nicegui import app, ui

# This example deliberately creates a separate FastAPI app and runs NiceGUI on top of it using `ui.run_with`.
# Please note that the `app` object from NiceGUI is also a FastAPI app.
# Often it is easier to stick to `ui.run` and use the `@app.get` etc. decorators to add normal FastAPI endpoints.
fastapi_app = FastAPI()


@fastapi_app.get('/')
def get_root():
    return {'message': 'Hello, FastAPI! Browse to /gui to see the NiceGUI app.'}


@ui.page('/')
def show():
    ui.label('Hello, NiceGUI!')

    # NOTE dark mode will be persistent for each user across tabs and server restarts
    ui.dark_mode().bind_value(app.storage.user, 'dark_mode')
    ui.checkbox('dark mode').bind_value(app.storage.user, 'dark_mode')


ui.run_with(
    fastapi_app,
    mount_path='/gui',  # NOTE this can be omitted if you want the paths passed to @ui.page to be at the root
    storage_secret='pick your private secret here',  # NOTE setting a secret is optional but allows for persistent storage per user
)

if __name__ == '__main__':
    uvicorn.run('main:fastapi_app', log_level='info', reload=True)

变化下 代码:

#!/usr/bin/env python3
import uvicorn
from fastapi import FastAPI
from nicegui import app, ui
import httpx

# 1. 创建FastAPI应用(官方方式)
fastapi_app = FastAPI(title="Hello NiceGUI 3.x 集成")

# 2. 添加纯FastAPI端点(不经过NiceGUI)
@fastapi_app.get("/api/hello")
async def hello_api(name: str = "Hello"):
    """纯FastAPI端点,返回JSON数据"""
    return {"message": f"你好, {name}!", "status": "success", "version": "3.x"}

@fastapi_app.get("/api/status")
async def status_api():
    """系统状态检查"""
    return {
        "status": "运行中",
        "service": "NiceGUI + FastAPI",
        "company": "Hello"
    }

@fastapi_app.post("/api/echo")
async def echo_api(data: dict):
    """回声测试"""
    return {"echo": data, "timestamp": "2026-01-01"}

# 3. NiceGUI页面定义
@ui.page('/')
def index():
    """主页面 - 仪表板"""
    ui.label('Hello NiceGUI 3.x 集成仪表板').style('''
        font-size: 24px;
        color: #ff6a00;
        font-weight: bold;
        margin-bottom: 20px;
    ''')

    # 状态卡片
    with ui.card().style('width: 100%; padding: 15px; background: #fff3e0;'):
        ui.label('系统状态').style('font-weight: bold; color: #ff6a00;')
        status_label = ui.label('等待检测...').style('margin-top: 5px;')

        async def check_status():
            try:
                async with httpx.AsyncClient() as client:
                    response = await client.get('http://localhost:8000/api/status')
                    data = response.json()
                    status_label.text = f"✓ {data['status']} - {data['service']}"
                    status_label.style('color: #28a745;')
                    ui.notify('系统正常', type='positive')
            except Exception as e:
                status_label.text = f"✗ 错误: {str(e)}"
                status_label.style('color: #dc3545;')
                ui.notify('连接失败', type='negative')

        ui.button('检查状态', on_click=check_status).style('margin-top: 10px;')

    # 功能导航
    ui.label('功能导航').style('font-size: 18px; margin-top: 20px; margin-bottom: 10px; font-weight: bold;')

    with ui.row():
        ui.link('API测试', '/api-test').style('''
            padding: 12px 24px;
            background: #ff6a00;
            color: white;
            text-decoration: none;
            border-radius: 6px;
            font-weight: bold;
        ''')

        ui.link('实时监控', '/monitor').style('''
            padding: 12px 24px;
            background: #0066cc;
            color: white;
            text-decoration: none;
            border-radius: 6px;
            font-weight: bold;
        ''')

        ui.link('数据可视化', '/visual').style('''
            padding: 12px 24px;
            background: #28a745;
            color: white;
            text-decoration: none;
            border-radius: 6px;
            font-weight: bold;
        ''')

@ui.page('/api-test')
def api_test():
    """API测试页面"""
    ui.label('API 端点测试').style('font-size: 20px; color: #0066cc;')
    ui.link('← 返回仪表板', '/').style('color: #666; margin-bottom: 15px; display: block;')

    # 测试结果展示区
    result = ui.label('').style('''
        padding: 15px;
        background: #f8f9fa;
        border-radius: 6px;
        min-height: 60px;
        margin: 10px 0;
        font-family: monospace;
    ''')

    # 输入框
    name_input = ui.input('名称').style('margin: 10px 0;')

    # 测试按钮组
    async def test_hello():
        try:
            async with httpx.AsyncClient() as client:
                name = name_input.value or "Hello"
                response = await client.get(f'http://localhost:8000/api/hello?name={name}')
                result.text = f"GET /api/hello\n{response.json()}"
                result.style('background: #d4edda; color: #155724;')
        except Exception as e:
            result.text = f"错误: {str(e)}"
            result.style('background: #f8d7da; color: #721c24;')

    async def test_status():
        try:
            async with httpx.AsyncClient() as client:
                response = await client.get('http://localhost:8000/api/status')
                result.text = f"GET /api/status\n{response.json()}"
                result.style('background: #d4edda; color: #155724;')
        except Exception as e:
            result.text = f"错误: {str(e)}"
            result.style('background: #f8d7da; color: #721c24;')

    async def test_echo():
        try:
            async with httpx.AsyncClient() as client:
                response = await client.post('http://localhost:8000/api/echo',
                                           json={"test": "data", "user": "Hello"})
                result.text = f"POST /api/echo\n{response.json()}"
                result.style('background: #d4edda; color: #155724;')
        except Exception as e:
            result.text = f"错误: {str(e)}"
            result.style('background: #f8d7da; color: #721c24;')

    with ui.row():
        ui.button('测试GET /api/hello', on_click=test_hello)
        ui.button('测试GET /api/status', on_click=test_status)
        ui.button('测试POST /api/echo', on_click=test_echo)

@ui.page('/monitor')
def monitor():
    """实时监控页面"""
    ui.label('实时监控').style('font-size: 20px; color: #0066cc;')
    ui.link('← 返回仪表板', '/').style('color: #666; margin-bottom: 15px; display: block;')

    # 监控指标
    with ui.row():
        metric1 = ui.label('CPU: --').style('font-size: 18px; color: #ff6a00;')
        metric2 = ui.label('内存: --').style('font-size: 18px; color: #0066cc;')
        metric3 = ui.label('请求: 0').style('font-size: 18px; color: #28a745;')

    status_log = ui.log().style('height: 150px; background: #f8f9fa; margin-top: 10px; padding: 10px;')

    async def simulate_monitoring():
        import asyncio
        import random
        status_log.push('🚀 开始监控...')

        for i in range(1, 11):
            cpu = random.randint(20, 80)
            mem = random.randint(30, 90)
            req = i * 10

            metric1.text = f'CPU: {cpu}%'
            metric2.text = f'内存: {mem}%'
            metric3.text = f'请求: {req}'

            status_log.push(f'[{i}] CPU:{cpu}% Mem:{mem}% Req:{req}')
            await asyncio.sleep(1)

        status_log.push('✅ 监控完成')
        ui.notify('监控周期结束', type='positive')

    ui.button('开始模拟监控', on_click=simulate_monitoring).style('margin-top: 10px;')

@ui.page('/visual')
def visual():
    """数据可视化页面"""
    ui.label('数据可视化').style('font-size: 20px; color: #28a745;')
    ui.link('← 返回仪表板', '/').style('color: #666; margin-bottom: 15px; display: block;')

    # 模拟图表数据
    with ui.row():
        value_label = ui.label('当前值: 0').style('font-size: 24px; font-weight: bold; color: #ff6a00;')

    # 进度条
    progress = ui.linear_progress(value=0).style('margin: 10px 0; width: 100%;')

    # 数据列表
    data_list = ui.label('').style('''
        padding: 10px;
        background: #f8f9fa;
        border-radius: 6px;
        min-height: 100px;
        font-family: monospace;
        white-space: pre-wrap;
    ''')

    async def generate_data():
        import asyncio
        import random
        import time

        data = []
        for i in range(1, 6):
            value = random.randint(10, 100)
            timestamp = time.strftime("%H:%M:%S")

            value_label.text = f'当前值: {value}'
            progress.set_value(i / 5)

            data.append(f"[{timestamp}] 数据点 #{i}: {value}")
            data_list.text = "\n".join(data)

            await asyncio.sleep(0.8)

        ui.notify('数据生成完成', type='positive')
        progress.set_value(1.0)

    ui.button('生成数据序列', on_click=generate_data).style('margin-top: 10px;')

# 4. 使用ui.run_with集成到FastAPI(官方推荐方式)
ui.run_with(
    fastapi_app,
    mount_path='/',  # NiceGUI挂载在根路径
    storage_secret='mi_mo_secret_key_2026',  # 会话存储密钥
    # 可选:自定义主题
    # dark=True,  # 默认暗色模式
)

# 5. 启动服务器(关键:移除 if __name__ 保护)
# 或改为:
if __name__ in {"__main__", "__mp_main__"}:
    print("=" * 50)
    print("🚀 Hello NiceGUI 3.x 集成服务启动")
    print("=" * 50)
    print("访问: http://localhost:8000")
    print("API文档: http://localhost:8000/docs")
    print("NiceGUI页面: http://localhost:8000")
    print("=" * 50)

    uvicorn.run('main:fastapi_app', host="0.0.0.0", port=8000, log_level="info", reload=False)

运行方式

# 1. 安装依赖
pip install fastapi nicegui uvicorn httpx

# 2. 保存为 main.py

# 3. 运行(关键:直接运行,不要加 if __name__ 保护)
python main.py

核心要点总结

  1. 1.ui.run_with() :将 NiceGUI 集成到 FastAPI
  2. 2.mount_path='/' :NiceGUI 在根路径
    1. @fastapi_app.get() :添加纯 API 端点
    1. @ui.page() :定义 GUI 页面
  3. 5.uvicorn.run() :启动整个应用
  4. 6.移除 if __name__ 或改为 if __name__ in {"__main__", "__mp_main__"}

访问地址

这个方案完全符合官方推荐,同时实现了单端口统一访问