Skip to main content

py-quick-webgui

最新版本: v0.2.11

Python Version License

使用 Python Web 框架快速构建桌面应用程序的工具库。

源码地址: https://gitee.com/iiixxxiii/pyquickwebgu

交流频道: https://pd.qq.com/s/8wqp6rkpk

✨ 特性

  • 🚀 多框架支持: Django, Flask, FastAPI, web.py
  • 🎨 前端集成: 支持 Vue, React 等现代前端框架
  • 🖥️ 多种浏览器: Command Browser, WebView, Tauri
  • 🔌 WebSocket 通信: 基于 WebSocket 协议的双向实时通信
  • 📦 系统托盘: 内置系统托盘图标和菜单,支持隐藏/显示/退出
  • 🔧 插件系统: 可扩展的插件架构
  • 🌐 跨平台: Windows, macOS, Linux
  • ✨ Eel 兼容: start() 方法与 Eel 框架 API 完全兼容
  • 🚀 延迟初始化: init() 方法支持动态配置
  • 🌐 自定义路由: route() 装饰器统一注册路由
  • ⚡ 快速启动: 智能服务器就绪检测 + 窗口延迟显示,消除白屏问题
  • 🖼️ 窗口图标: 新增 icon 参数,自动加载 favicon.ico / logo.ico 或用户自定义图标
  • 🌐 外部开发服务器: index_html 支持外部 URL(如 Vite/Webpack 开发服务器),方便前后端分离开发

📋 目录

📦 安装

基础安装

pip install pyquickwebgui

完整依赖

根据不同需求安装额外依赖:

# Flask WebSocket 支持
pip install flask-socketio eventlet

# FastAPI (已包含)
pip install uvicorn

# web.py (已包含)
pip install web.py

# WebView 支持
pip install pywebview

# 系统托盘支持
pip install pystray Pillow

# 数据库加密支持(可选,需 C++ 构建工具)
pip install pysqleet

# Tauri 支持
# 请参考 Tauri 官方文档

🚀 快速开始

1. Hello World 示例

创建 hello.py:

from pyquickwebgui import QuikeUI
import os

# 创建 QuikeUI 实例
qui = QuikeUI(
    web_path=os.path.join(os.path.dirname(__file__), "web"),
    index_html="hello.html"  # 启动页面
)

# 暴露函数给 JavaScript
@qui.expose
def say_hello(name):
    print(f'Hello from {name}')
    return f'Python says: Hello, {name}!'

# 启动应用
qui.run()

创建 web/hello.html:

<!DOCTYPE html>
<html>
<head>
    <title>Hello World</title>
    <script src="/QuikeUI.js"></script>
    <script>
        // 调用 Python 函数
        window.addEventListener('load', async function() {
            try {
                const result = await QuikeUI.callPython('say_hello', 'JavaScript!');
                alert(result);
            } catch (error) {
                console.error('调用失败:', error);
            }
        });
    </script>
</head>
<body>
    <h1>Hello from QuikeUI!</h1>
</body>
</html>

运行:

python hello.py

2. Hello World PyPage 示例(纯 Python 构建 UI)

使用 PagePlugin 完全在 Python 中构建 UI,无需编写 HTML 文件。通过 view() + CSS 类名模拟各种元素,on_click + on_result 实现 Lona 风格事件驱动交互:

from pyquickwebgui import QuikeUI
from pyquickwebgui.rootplugins.PagePlugin import PagePlugin as page
import random
import datetime

# 创建 QuikeUI 实例
qui = QuikeUI(
    title="QuikeUI 极简示例",
    debug=True,
)

# 创建 Page 实例(每个页面独立状态)
home_page = page.create_page()


# ==================== 按钮回调函数 ====================

def handle_get_info(data):
    """按钮回调 - 获取服务器信息"""
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"服务器时间: {now}\nPython 运行正常!"


def handle_get_random(data):
    """按钮回调 - 生成随机数"""
    return f"随机数: {random.randint(1, 100)}"


def show_in_input(result):
    """结果回调 - 将结果写入 input 输入框(Python 完全控制 UI)"""
    if result and result.get('status') == 'success':
        home_page.set_value('result_input', str(result.get('result', '')))


def show_as_layer(result):
    """结果回调 - 将结果用 layer 弹窗展示"""
    if result and result.get('status') == 'success':
        home_page.layer_msg(str(result.get('result', '')), icon=1)


# ==================== 首页 ====================

@qui.route("/")
def index():
    container = home_page.view(cls="layui-container")

    # 标题 - 用 view() + CSS 类名模拟 h1、blockquote 等元素
    container.add(home_page.view("QuikeUI + PagePlugin 极简示例", cls="h1"))
    container.add(home_page.view("Python 与 JavaScript 双向通信示例", cls="layui-elem-quote"))

    # 按钮区域
    container.add(home_page.view("Python 交互(Lona 风格)", cls="h3"))
    btn_container = home_page.view(cls="layui-btn-container")

    # 按钮1:结果写入 input 输入框
    btn1 = home_page.button("获取服务器信息", btn_type="primary",
                            on_click=handle_get_info, on_result=show_in_input)
    btn_container.add(btn1)

    # 按钮2:结果用 layer 弹窗展示
    btn2 = home_page.button("生成随机数", color="blue",
                            on_click=handle_get_random, on_result=show_as_layer)
    btn_container.add(btn2)
    container.add(btn_container)

    # 结果展示输入框(需要真正的 <input> 元素时用 tags)
    container.add(home_page.view("结果展示", cls="h3"))
    result_input = home_page.tags.input_(
        id="result_input", type="text",
        cls="layui-input", placeholder="点击按钮后,结果将显示在这里...",
        readonly="readonly"
    )
    container.add(result_input)

    # 链接 - 用 link() 方法
    container.add(home_page.view("组件示例", cls="h3"))
    container.add(home_page.link(" 进入组件示例首页", href="/demo/",
                                  cls="layui-btn layui-btn-normal"))

    # 渲染页面
    body = home_page.view()
    body.add(container)
    return home_page.render_page(body, title="QuikeUI 极简示例", include_quikeui=True)


if __name__ == "__main__":
    qui.run()

运行:

python hello.py

核心要点:

  • view("文本", cls="h1") — view() 始终创建 div,通过 CSS 类名模拟 h1/h2/p/blockquote 等元素
  • on_click 处理业务逻辑,on_result 控制 UI 更新方式(Python 完全控制)
  • link("文本", href="...") — 封装 a 标签,支持绑定 Python 回调
  • 只有需要真正的 <input>/<table> 等原生元素时,才用 tags.xxx

查看完整示例:examples/18 - hello_world_pypage

3. Flask 示例

from flask import Flask
from pyquickwebgui import QuikeUI

app = Flask(__name__)

@app.route('/')
def home():
    return '<h1>Hello Flask!</h1>'

qui = QuikeUI(
    app=app,
    server_type="flask",
    width=800,
    height=600
)

qui.run()

4. FastAPI 示例

from fastapi import FastAPI
from pyquickwebgui import QuikeUI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello FastAPI!"}

qui = QuikeUI(
    app=app,
    server_type="fastapi",
    debug=True
)

qui.run()

🔧 核心功能

WebSocket 双向通信

QuikeUI 使用基于 eel 协议的 WebSocket 实现 Python 和 JavaScript 的双向实时通信。

架构优势

  1. 实时双向通信:Python 和 JavaScript 可以互相调用函数
  2. 低延迟:WebSocket 保持长连接,避免 HTTP 请求开销
  3. 自动函数注册:前端自动获取后端暴露的函数列表
  4. 异步支持:FastAPI 支持异步函数调用

通信协议

消息格式:

  • JavaScript → Python(函数调用):
{"call": 1234567890, "name": "function_name", "args": ["arg1", "arg2"]}
  • Python → JavaScript(返回结果):
{"return": 1234567890, "status": "ok", "value": "result_data"}
  • Python → JavaScript(发送函数列表):
{"type": "py_functions", "functions": ["func1", "func2"]}

Python 调用 JavaScript

QuikeUI 支持两种浏览器类型的 JS 调用:

1. WebView 模式

# webview 直接执行 JS
qui.call_js_function('jsFunction', 'arg1', 'arg2')

2. CommandBrowser 模式(通过 WebSocket)

# 方式1: 直接调用
qui.call_js_function('my_js_function', 'arg1', 'arg2')

# 方式2: 使用 _js 后缀
qui.say_hello_js('Hello from Python!')

# 方式3: 在启动回调中调用(推荐)
import time
def on_startup():
    time.sleep(2)  # 等待 WebSocket 连接建立
    qui.my_function_js('test')

qui.on_startup = on_startup
qui.run()

⚠️ 重要提示:

  • Python 调用 JS 前,必须确保 WebSocket 连接已建立
  • 建议在 on_startup 回调中使用 time.sleep() 等待连接
  • 启用调试模式查看详细日志:qui = QuikeUI(debug=True)

工作流程:

Python (call_js_function)
    ↓
查找 WebSocket Handler
    ↓
构建 WebSocket 消息帧
    ↓
发送到所有活跃的客户端连接
    ↓
浏览器接收消息 (QuikeUI.js onmessage)
    ↓
执行对应的 JavaScript 函数

JavaScript 调用 Python

自动队列缓存机制:

QuikeUI 支持在 WebSocket 连接前缓存调用,连接后自动执行。这解决了页面加载时 WebSocket 可能尚未建立的问题。

// ✅ 即使 WebSocket 未连接,也可以安全调用
// 调用会被加入队列,等待连接后自动发送
QuikeUI.callPython('get_data').then(result => {
    console.log(result);
});

// 多个调用会被依次缓存和执行
QuikeUI.callPython('func1', 'arg1');  // 加入队列
QuikeUI.callPython('func2', 'arg2');  // 加入队列
QuikeUI.callPython('func3', 'arg3');  // 加入队列

// WebSocket 连接后,按顺序执行:func1 → func2 → func3

工作原理:

  1. 连接前:调用被加入 _mock_queue 队列
  2. 连接时:触发 onopen 事件,设置 _websocket_connected = true
  3. 刷新队列:调用 _flush_mock_queue() 依次发送所有缓存的调用
  4. 返回结果:每个调用都会收到对应的 Promise 结果

优势:

  • ✅ 无需手动等待 WebSocket 连接
  • ✅ 无需使用 setTimeout 或轮询检查
  • ✅ 调用顺序保证(FIFO)
  • ✅ 错误处理完善(发送失败会重新入队)

详细实现说明:

队列机制的核心是 _call_object 创建调用对象,包含唯一的 call_id、函数名和参数。当 WebSocket 未连接时,这些调用对象被推入 _mock_queue 数组。连接成功后,_flush_mock_queue() 方法会遍历队列,将每个调用序列化为 JSON 并通过 WebSocket 发送。

// 内部实现示例
QuikeUI._mock_queue = [];  // 调用队列
QuikeUI._call_return_callbacks = {};  // 回调映射

QuikeUI.callPython = function(name, ...args) {
    if (!QuikeUI._websocket || !QuikeUI._websocket_connected) {
        let call_object = QuikeUI._call_object(name, args);
        QuikeUI._mock_queue.push(call_object);  // 入队
        return new Promise(function(resolve, reject) {
            QuikeUI._call_return_callbacks[call_object.call] = {resolve, reject};
        });
    }
    // 已连接则直接发送
    ...
};

这种设计确保了即使在网络不稳定或服务器重启的情况下,前端调用也不会丢失,而是会在连接恢复后自动重试。

Python 同步调用 JavaScript

除了异步调用,QuikeUI 还支持同步调用 JavaScript 函数并等待返回值:

# 同步调用 JS 函数(阻塞直到收到响应)
result = qui.call_js_function_sync("js_random", timeout=5.0)
print(f'从 JavaScript 获取到: {result}')

重要注意事项:

  1. WebSocket 连接时机

    同步调用必须在 WebSocket 连接建立之后 才能执行:

    # ❌ 错误:在 on_startup 中调用(连接尚未建立)
    def on_startup():
        result = qui.call_js_function_sync("js_func")  # RuntimeError!
    
    # ✅ 正确:在 qui.run() 后等待一段时间
    qui.run()
    time.sleep(5)  # 等待浏览器和 WebSocket 连接
    result = qui.call_js_function_sync("js_func")
    
  2. 与异步调用的区别

    特性 同步调用 异步调用
    方法名 call_js_function_sync() call_js_function()
    阻塞 ✅ 阻塞当前线程 ❌ 不阻塞
    返回值 直接返回结果 需要通过回调或 Promise
    适用场景 需要立即获取结果的场景 不需要等待结果的场景
  3. 异常处理

    try:
        result = qui.call_js_function_sync("js_func", timeout=5.0)
        print(f'结果: {result}')
    except TimeoutError:
        print('调用超时')
    except RuntimeError as e:
        print(f'调用失败: {e}')
    

技术实现:

同步调用的实现原理:

  1. Python 端生成唯一的 call_id
  2. 通过 WebSocket 发送调用请求(带 sync: true 标记)
  3. 将响应存储在 _js_call_responses 字典中
  4. 轮询检查响应是否到达(每 50ms)
  5. 收到响应后返回结果或抛出异常

服务器端(DefaultServerWebpy)会检测 sync 标记并将结果存储到 QuikeUI 实例的响应字典中,同时也会通过 WebSocket 发送标准响应(保持向后兼容)。

自动重连机制:

QuikeUI 内置了强大的 WebSocket 自动重连功能,确保在网络波动或服务器重启时应用能够自动恢复连接。

// 默认启用,每2秒重试,最多10次
// 可自定义配置
QuikeUI.enable_reconnect(
    interval = 3000,     // 重连间隔(毫秒)
    max_attempts = 5     // 最大重连次数(0=无限)
);

// 禁用自动重连
QuikeUI.disable_reconnect();

// 手动触发重连
QuikeUI.manual_reconnect();

重连特性:

  • ✅ 连接断开后自动尝试重连
  • ✅ 可配置重连间隔和最大次数
  • ✅ 重连成功后自动刷新队列
  • ✅ 达到最大次数后拒绝待处理 Promise
  • ✅ 支持手动控制和禁用
  • ✅ 详细的日志输出和告警提示

工作流程:

连接断开 (onclose)
    ↓
检查是否启用自动重连
    ├─ 否 → 结束
    └─ 是 ↓
检查是否达到最大次数
    ├─ 是 → 拒绝所有待处理 Promise,显示告警
    └─ 否 ↓
增加重连计数
    ↓
延迟指定时间
    ↓
关闭旧连接(如果存在)
    ↓
创建新 WebSocket 连接
    ↓
连接成功 → 重置计数,刷新队列
连接失败 → 再次触发 onclose,循环

使用场景:

  1. 网络不稳定环境
QuikeUI.enable_reconnect(5000, 20);  // 每5秒重试,最多20次
  1. 服务器维护期间
QuikeUI.enable_reconnect(3000, 0);  // 无限重连
  1. 用户登出时
function logout() {
    QuikeUI.disable_reconnect();  // 禁用重连
    QuikeUI.callPython('logout').then(() => {
        window.location.href = '/login';
    });
}

告警功能:

当达到最大重连次数时,系统会提供多层告警:

  1. 控制台告警:详细的错误信息和排查建议
  2. 浏览器通知:如果用户授予权限,显示系统通知
  3. 自定义事件:监听 qui_reconnect_failed 事件进行自定义处理
  4. Promise 拒绝:所有待处理的 Promise 会被拒绝,带有详细错误信息
// 监听重连失败事件
window.addEventListener('qui_reconnect_failed', function(event) {
    console.error('重连失败:', event.detail);
    // event.detail: {attempts, maxAttempts, message}
    showCustomAlert(event.detail);
});

// 请求通知权限
if ('Notification' in window) {
    Notification.requestPermission().then(permission => {
        if (permission === 'granted') {
            console.log('✅ 已启用浏览器通知');
        }
    });
}

最佳实践:

  • 生产环境:QuikeUI.enable_reconnect(3000, 10) - 适中的重连策略
  • 开发环境:QuikeUI.enable_reconnect(1000, 5) - 快速重连便于调试
  • 关键业务:QuikeUI.enable_reconnect(5000, 0) - 更长等待,无限重试

性能考虑:

  • 重连间隔不宜过短(建议 ≥ 1000ms)
  • 避免频繁的手动重连
  • 监控重连次数,及时发现网络问题
// 查看重连状态
console.log('重连启用:', QuikeUI._reconnect_enabled);
console.log('当前次数:', QuikeUI._reconnect_attempts);
console.log('连接状态:', QuikeUI._websocket_connected);

参数配置

QuikeUI 支持丰富的配置选项:

qui = QuikeUI(
    # 服务器配置
    server_type="webpy",          # 服务器类型: webpy, flask, fastapi
    app=None,                      # Web 应用实例
    port=None,                     # 端口号(自动分配)
    
    # 窗口配置
    width=800,                     # 窗口宽度
    height=600,                    # 窗口高度
    fullscreen=False,              # 全屏模式
    frameless=False,               # 无边框窗口
    x=0, y=0,                      # 窗口位置
    
    # 浏览器配置
    browser_type="command",        # 浏览器类型: command, webview, tauri
    show_browser=True,             # 显示浏览器
    browser_path=None,             # 浏览器路径
    extra_flags=[],                # 额外浏览器标志
    
    # 页面配置
    index_html="index.html",       # 启动页面(支持文件名或外部 URL,如 http://localhost:5173/)
    web_path="web",               # 静态文件路径(默认值:"web",支持相对路径和绝对路径)
    
    # 窗口图标
    icon=None,                     # 窗口图标路径(.ico 格式,支持绝对路径或相对于 web_path 的相对路径)
    
    # 高级功能
    inject_js=None,                # 注入 JavaScript(支持文件路径、相对路径、代码字符串)
    disable_right_click=False,     # 禁用右键菜单
    debug=False,                   # 调试模式
    reload=False,                  # 热重载
    
    # 生命周期
    on_startup=None,               # 启动回调
    on_shutdown=None,              # 关闭回调
    
    # 其他
    tray=None,                    # 系统托盘配置
    log=None,                      # 日志对象
    log_level="info",              # 日志级别
)

web_path 路径处理:

  • ✅ 默认值:web_path="web",无需手动设置即可使用 web/ 目录
  • ✅ 支持相对路径:web_path="web" 会自动转换为绝对路径
  • ✅ 智能查找:优先在当前工作目录查找,如果不存在则在脚本所在目录查找
  • ✅ 自动转换:相对路径会在初始化时自动转换为绝对路径
  • ✅ 简化配置:无需手动使用 os.path.join() 拼接路径
  • ✅ 外部 URL 兼容:当 index_html 为外部 URL 时,web_path 自动转换为绝对路径(用于图标等资源查找)
# 方式 1: 使用默认值(推荐)
qui = QuikeUI()  # web_path 默认为 "web"
qui.run()

# 方式 2: 显式指定相对路径
qui = QuikeUI(web_path="web")
qui.run()

# 方式 3: 使用绝对路径
qui = QuikeUI(web_path="/absolute/path/to/web")
qui.run()

# 方式 4: 使用 init() 方法
qui = QuikeUI()
qui.init(web_path="web")  # 支持相对路径
qui.run()

inject_js 参数详解

inject_js 参数支持三种类型,灵活注入 JavaScript 代码:

1. 相对路径(推荐)

从 web_path 目录下查找文件:

# 方式 1: 简单文件名
qui = QuikeUI(
    web_path="web",
    inject_js="menu.js"  # 会查找 web/menu.js
)

# 方式 2: 子目录下的文件
qui = QuikeUI(
    web_path="web",
    inject_js="scripts/custom.js"  # 会查找 web/scripts/custom.js
)

2. 绝对路径

直接使用完整的文件路径:

import os

qui = QuikeUI(
    inject_js=r"D:\projects\menu.js"  # Windows 路径
)

# 或使用 os.path.join
qui = QuikeUI(
    inject_js=os.path.abspath("menu.js")
)

3. JavaScript 代码字符串

直接传入 JavaScript 代码:

qui = QuikeUI(
    inject_js="console.log('Hello from Python!');"
)

# 多行代码
qui = QuikeUI(
    inject_js="""
        console.log('初始化完成');
        window.customConfig = { debug: true };
    """
)

注意事项:

  • ✅ 如果 HTML 中已通过 <script> 标签加载了 JS 文件,建议设置 inject_js=None 避免重复加载
  • ✅ 相对路径会自动从 web_path 目录下查找
  • ✅ 如果文件不存在,会在日志中记录警告信息

系统托盘

QuikeUI 内置了系统托盘功能,无需额外插件即可创建托盘图标和菜单。

基本用法

from pyquickwebgui import QuikeUI

qui = QuikeUI(
    title="我的应用",
    icon="favicon.ico",          # 窗口图标
    tray={
        "icon": "favicon.ico",   # 托盘图标(相对于 web_path 或绝对路径)
    }
)

qui.run()

默认菜单

未指定 menu 时,自动创建以下默认菜单:

菜单项 功能
隐藏 隐藏主窗口
显示 显示主窗口
─── 分隔线
退出 关闭应用程序

自定义菜单和回调

方式 1:字典格式(推荐,更简洁)

def on_show():
    print("显示窗口")

def on_exit():
    QuikeUI.close_application()

def custom_action():
    print("自定义操作")

qui = QuikeUI(
    title="我的应用",
    tray={
        "icon": "favicon.ico",
        "menu": [
            {"text": "显示窗口", "action": on_show},
            {"text": "自定义操作", "action": custom_action},
            {"text": "退出", "action": on_exit},
        ]
    }
)

方式 2:pystray.MenuItem 格式

import pystray

# 自定义菜单回调
def on_show():
    print("自定义显示逻辑")

def on_exit():
    print("自定义退出逻辑")
    QuikeUI.close_application()

# 自定义菜单
custom_menu = (
    pystray.MenuItem("显示窗口", lambda: on_show()),
    pystray.Menu.SEPARATOR,
    pystray.MenuItem("退出", lambda: on_exit()),
)

qui = QuikeUI(
    title="我的应用",
    tray={
        "icon": "favicon.ico",
        "name": "MyApp",
        "title": "我的应用提示",
        "menu": custom_menu,             # 自定义菜单
        "menu_click_show": on_show,      # 默认菜单的显示回调
        "menu_click_exit": on_exit,      # 默认菜单的退出回调
    }
)

提示:字典格式中每个菜单项支持 text(菜单文本)、action(回调函数)、enabled(是否可用,默认 True)、visible(是否可见,默认 True)字段。

配合 JavaScript 控制窗口

@qui.expose
def hide_win():
    qui.hide()          # 隐藏窗口
    return {"message": "ok"}

@qui.expose
def close_win():
    QuikeUI.close_application()  # 关闭应用
    return {"message": "ok"}
// JavaScript 中调用
await QuikeUI.callPython('hide_win');   // 隐藏窗口
await QuikeUI.callPython('close_win');  // 关闭窗口

查看完整示例:examples/14 - tray_web

窗口图标设置

QuikeUI 支持通过 icon 参数自定义窗口图标,优先级如下:

  1. 用户指定的 icon 参数:支持绝对路径或相对于 web_path 的相对路径
  2. 项目默认图标:src/pyquickwebgui/logo.ico
  3. 系统默认图标:如果以上都不存在
# 方式 1: 相对路径(相对于 web_path)
qui = QuikeUI(icon="favicon.ico")

# 方式 2: 绝对路径
qui = QuikeUI(icon=r"D:\projects\my_icon.ico")

# 方式 3: 不指定,使用项目默认 logo.ico
qui = QuikeUI()

Windows 平台说明:

  • WebView 模式下通过 .NET WinForms API 设置窗口标题栏图标
  • CommandBrowser 模式下图标由浏览器进程决定
  • 打包后的 exe 文件需要在编译时通过资源文件设置程序图标

插件系统

QuikeUI 支持插件扩展:

from pyquickwebgui.rootplugins.BasePlugin import BasePlugin

class MyPlugin(BasePlugin):
    def register(self, app):
        app.log.info("MyPlugin registered")
    
    def run(self, app):
        app.log.info("MyPlugin running")

PagePlugin UI 组件库

PagePlugin 提供了基于 Layui 风格的 UI 组件库,使用 dominate 库生成 HTML 标签,方便在 Python 中快速构建 Web 界面。

安装依赖

pip install dominate

基本用法

from pyquickwebgui.rootplugins.PagePlugin import PagePlugin as page

# 生成按钮
btn = page.button("提交", btn_type="primary")

# 生成输入框
inp = page.input("username", placeholder="请输入用户名", verify="required")

# 生成下拉选择框
options = [("0", "写作"), ("1", "阅读"), ("2", "游戏")]
sel = page.select("interest", options, selected_value="1")

# 生成表单项
form_item = page.form_item("用户名", inp)

通用视图方法

view() 是统一的 DOM 元素创建入口,始终创建 div 元素,通过 cls 参数指定 CSS 类名来模拟各种 UI 元素。绝大多数场景下不需要直接使用 tags.xxx,view() + CSS 类名即可构建所有界面:

# 创建 Page 实例
my_page = page.create_page()

# 普通容器
container = my_page.view(cls="layui-container")

# 模拟标题(通过 CSS 类名控制样式)
my_page.view("主标题", cls="h1")
my_page.view("副标题", cls="h2")
my_page.view("小标题", cls="h3")

# 引用块
my_page.view("引用文字", cls="layui-elem-quote")

# 段落
my_page.view("段落内容", cls="p")

# 徽章
my_page.view("标签", cls="layui-badge")
my_page.view("圆角标签", cls="layui-badge layui-badge-radius")

# 按钮(也可以用 page.button() 获得更丰富的参数)
my_page.view("按钮文本", cls="layui-btn layui-btn-primary")

# 嵌套结构(卡片示例)
parent = my_page.view(cls="layui-card")
parent.add(my_page.view("卡片标题", cls="layui-card-header"))
parent.add(my_page.view("卡片内容", cls="layui-card-body"))

# 行内布局
row = my_page.view(cls="layui-row")
col1 = my_page.view("左侧", cls="layui-col-md6")
col2 = my_page.view("右侧", cls="layui-col-md6")
row.add(col1)
row.add(col2)

view() + CSS 类名对照表:

想模拟的元素 view() 写法 说明
<h1> view("文本", cls="h1") Layui 内置 h1 样式
<h2> view("文本", cls="h2") Layui 内置 h2 样式
<h3> view("文本", cls="h3") Layui 内置 h3 样式
<p> view("文本", cls="p") Layui 内置 p 样式
<blockquote> view("文本", cls="layui-elem-quote") Layui 引用块样式
<span> 徽章 view("文本", cls="layui-badge") Layui 徽章样式
<button> view("文本", cls="layui-btn") Layui 按钮样式
<div> 容器 view(cls="layui-container") Layui 容器
<div> 卡片 view(cls="layui-card") Layui 卡片
行内布局 view(cls="layui-row") Layui 栅格行
列 view(cls="layui-col-md6") Layui 栅格列

设计理念: view() + CSS 类名 = 万能元素构建器。保持代码风格统一,所有 UI 元素都用同一种方式创建。常用组件(如 button()、input()、table() 等)仍然保留专用方法以提供更便捷的参数接口。

何时需要 tags.xxx: 只有当需要真正的原生 HTML 元素时(如 <input>、<table>、<select> 等表单元素或表格结构),才需要使用 my_page.tags.xxx()。

link() 方法封装了 a 标签,支持绑定 Python 回调:

# 普通链接
my_page.link("进入首页", href="/home", cls="layui-btn layui-btn-normal")

# 新窗口打开
my_page.link("外部链接", href="https://example.com", target="_blank")

# 绑定 Python 回调(WebSocket 通信)
def handle_click(data):
    return {"status": "ok", "msg": "链接被点击"}

my_page.link("WebSocket 链接", href="#", on_click=handle_click)
方法 说明 主要参数
view() 通用视图容器 content, cls, style
link() 链接(a 标签) text, href, target, cls, on_click, on_result
container() layui 容器 content, fluid

表单组件

方法 说明 主要参数
button() 按钮 text, btn_type, disabled
input() 输入框 name, input_type, placeholder, disabled, value, verify
select() 下拉选择框 name, options, selected_value, disabled, searchable
checkbox() 复选框 name, title, checked, disabled, skin
checkboxes() 复选框组 name, options, checked_values, disabled
switch() 开关 name, checked, disabled, on_text, off_text
radio() 单选框组 name, options, checked_value, disabled
textarea() 文本域 name, placeholder, rows, disabled, value
form_item() 表单项 label, content, inline
form() 表单容器 content, action, pane, filter_name

展示组件

方法 说明 主要参数
table() 表格 elem, cols, data, page, limit, url
laypage() 分页 elem, count, limit
tree_table() 树形表格 elem, cols, data
nav() 导航菜单 items, active, filter_name
menu() 基础菜单 items, active_index
tabs() 标签页 items, active_index, filter_name
badge() 徽章 text, badge_type
blockquote() 引用块 content, cite
fieldset() 字段集 legend, content
hr() 分割线 -
panel() 面板 title, content, collapsed
collapse() 折叠面板 items
progress() 进度条 percent, theme
timeline() 时间线 items
tree() 树组件 elem, data
carousel() 轮播 elem, items, autoplay, interval
code() 代码预览 content, language

交互组件

方法 说明 主要参数
layer() 弹出层 elem, title, content, area, btn
laydate() 日期选择器 elem, type, format, value
upload() 上传组件 elem, url, accept, exts, multiple
dropdown() 下拉菜单 elem, items, trigger
colorpicker() 颜色选择器 elem, color, format
transfer() 穿梭框 elem, data, title, value
slider() 滑块 elem, value, min_val, max_val, step
rate() 评分 elem, value, count, half

Page 实例 - Lona 风格事件驱动

Page 类提供类似 Lona 的事件驱动交互模式,Python 端完全控制 UI 更新:

from pyquickwebgui.rootplugins.PagePlugin import PagePlugin as page

# 创建 Page 实例(每个页面独立状态)
home_page = page.create_page()

# 定义业务回调
def get_server_time(data):
    import datetime
    return f"服务器时间: {datetime.datetime.now()}"

# 定义 UI 更新回调(Python 完全控制展示方式)
def show_in_input(result):
    if result and result.get('status') == 'success':
        home_page.set_value('result_input', result['result'])

def show_as_popup(result):
    if result and result.get('status') == 'success':
        home_page.layer_msg(result['result'])

# 按钮绑定:on_click 处理业务,on_result 控制展示
btn1 = home_page.button("获取时间", on_click=get_server_time, on_result=show_in_input)
btn2 = home_page.button("弹窗显示", on_click=get_server_time, on_result=show_as_popup)

Page UI 操作方法:

方法 说明 示例
set_value(id, value) 设置元素 value page.set_value('input_1', 'hello')
set_text(id, text) 设置元素文本 page.set_text('label_1', 'world')
set_html(id, html) 设置元素 innerHTML page.set_html('div_1', '<b>bold</b>')
layer_msg(msg, icon) layer.msg 弹窗 page.layer_msg('操作成功', icon=1)
layer_alert(msg, icon) layer.alert 弹窗 page.layer_alert('提示内容')

事件驱动流程:

用户点击按钮
    ↓
JS 通过 WebSocket 调用 Python (on_click 回调)
    ↓
Python 执行业务逻辑,返回结果
    ↓
Python 执行 on_result 回调,接收结果
    ↓
on_result 回调通过 WebSocket 推送 JS 更新 UI
    ↓
前端实时展示结果(弹窗/输入框/DOM 更新)

设计理念:

  • ✅ JS 只做最小化通信,不包含任何 UI 逻辑
  • ✅ Python 端完全控制 UI 更新目标(想弹窗就弹窗,想写入输入框就写入输入框)
  • ✅ on_result 支持任意 Python callable,展示逻辑与业务逻辑完全解耦

完整示例

from pyquickwebgui.rootplugins.PagePlugin import PagePlugin as page

# 创建 Page 实例
my_page = page.create_page()

# 创建表单内容(使用 view() 替代 tags.div)
content = my_page.view()

# 添加输入框
username_input = my_page.input("username", placeholder="请输入用户名", verify="required")
content.add(my_page.form_item("用户名", username_input))

# 添加下拉选择框
options = [("0", "写作"), ("1", "阅读"), ("2", "游戏")]
select_box = my_page.select("interest", options, selected_value="1")
content.add(my_page.form_item("兴趣爱好", select_box))

# 添加开关
sw = my_page.switch("status", checked=True, on_text="开", off_text="关")
content.add(my_page.form_item("状态", sw))

# 添加单选框
radio_options = [("male", "男"), ("female", "女")]
radio_group = my_page.radio("gender", radio_options, checked_value="male")
content.add(my_page.form_item("性别", radio_group))

# 添加文本域
ta = my_page.textarea("description", placeholder="请输入描述", rows=5)
content.add(my_page.form_item("描述", ta))

# 添加按钮
btn = my_page.button("提交", btn_type="primary")
content.add(btn)

# 包装成表单
form = my_page.form(content, action="/submit", filter_name="demo")

# 输出 HTML
print(form.render())

@qui.expose 装饰器

@qui.expose 装饰器允许将 Python 函数暴露给 JavaScript 调用,实现前后端的双向通信。

Python 端用法

from pyquickwebgui import QuikeUI

# 创建 QuikeUI 实例
qui = QuikeUI()

# 使用 @qui.expose 装饰器暴露函数
@qui.expose
def say_hello(name):
    print(f'Hello from {name}')
    return f'Python says: Hello, {name}!'

# 启动应用
qui.run()

JavaScript 端用法

在 HTML 文件中引入 QuikeUI.js:

<script src="/QuikeUI.js"></script>
<script>
    // 调用暴露的 Python 函数
    QuikeUI.callPython('say_hello', 'JavaScript World!')
        .then(result => {
            console.log('Python 返回:', result);
        })
        .catch(error => {
            console.error('调用失败:', error);
        });
</script>

API 参考

Python API:

  • @qui.expose - 将 Python 函数标记为可被 JavaScript 调用
  • qui.get_exposed_functions() - 获取所有已暴露的函数
  • qui.call_js_function(func_name, *args, **kwargs) - 调用 JavaScript 函数(需要浏览器支持)

JavaScript API:

  • QuikeUI.callPython(funcName, ...args) - 调用暴露的 Python 函数,返回 Promise
  • QuikeUI.expose(func) - 暴露 JavaScript 函数给 Python(当前版本暂不支持)

示例:

// 无参数调用
QuikeUI.callPython('get_time')
    .then(time => console.log(time));

// 带参数调用
QuikeUI.callPython('add_numbers', 5, 10)
    .then(result => console.log(result)); // 输出: 15

// 异步处理
async function fetchData() {
    const result = await QuikeUI.callPython('get_data', 'param1');
    console.log(result);
}

工作原理

  1. 装饰器注册:@qui.expose 将函数添加到 _exposed_functions 字典和 exposed_functions_list 列表
  2. WebSocket 连接:前端通过 WebSocket 连接到后端服务器
  3. 函数列表同步:连接建立后,后端发送所有暴露函数的列表给前端
  4. 动态导入:前端根据函数列表动态创建对应的 JavaScript 函数
  5. 调用执行:JavaScript 调用时,通过 WebSocket 发送请求到后端
  6. 结果返回:后端执行函数并将结果以 JSON 格式返回

支持的 Web 框架

Flask

依赖安装:

pip install flask-socketio eventlet

特点:

  • 使用 flask-socketio 实现 WebSocket
  • 自动降级到普通 Flask 服务器(如果不支持 WebSocket)
  • 不支持 waitress(因为它不支持 WebSocket)

FastAPI

依赖安装:

pip install uvicorn

特点:

  • 原生支持 WebSocket
  • 支持异步函数(async def)
  • 性能更好

web.py

依赖安装:

# 无需额外依赖,使用内置 socket 实现

特点:

  • 使用原生 socket + threading 实现 WebSocket
  • WebSocket 运行在独立端口(HTTP 端口 + 1)
  • 完全手动实现 WebSocket 协议
  • 适合轻量级应用

注意:

  • web.py 的 WebSocket 端口为 HTTP 端口 + 1
  • 例如:HTTP 在 8080,WebSocket 在 8081
  • 前端需要连接到正确的 WebSocket 端口

工作流程

  1. 应用启动

    • QuikeUI 初始化,收集所有 @qui.expose 装饰的函数
    • 将函数列表传递给服务器配置
  2. 服务器启动

    • Flask/FastAPI/web.py 服务器启动
    • 注册 WebSocket 端点 /qui
    • 构建暴露函数字典
  3. 客户端连接

    • 浏览器加载页面,建立 WebSocket 连接
    • 后端发送 py_functions 消息,包含所有暴露的函数名
    • 前端动态创建对应的 JavaScript 函数代理
  4. 函数调用

    • JavaScript 调用 QuikeUI.callPython('function_name', args)
    • 生成唯一的 call_id
    • 通过 WebSocket 发送调用请求
    • Python 执行函数并返回结果
    • JavaScript 接收结果并触发回调/Promise

注意事项

  1. 依赖安装

    • Flask: 需要 flask-socketio 和 eventlet 或 gevent
    • FastAPI: 需要 uvicorn
  2. 函数序列化

    • 参数和返回值必须是 JSON 可序列化的
    • 复杂对象需要自定义序列化
  3. 异步支持

    • FastAPI 支持 async def 函数
    • Flask 仅支持同步函数
  4. 错误处理

    • 所有异常都会被捕获并通过 WebSocket 返回
    • 建议在 Python 函数中添加适当的错误处理
  5. 连接管理

    • WebSocket 断开时会自动重连(取决于浏览器实现)
    • 可以在后端监听 disconnect 事件
  6. WebSocket 时序

    • Python 调用 JS 前必须等待连接建立
    • 建议使用 on_startup 回调 + time.sleep()
    • 启用 debug=True 查看连接状态日志

迁移指南

如果你之前使用 HTTP API 方式:

之前(HTTP):

fetch('/api/exposed/say_hello', {
    method: 'POST',
    body: JSON.stringify({args: ['World']})
})

现在(WebSocket):

QuikeUI.callPython('say_hello', 'World').then(result => {
    console.log(result);
});

更简洁、更高效!🎉

注意事项

  1. 函数命名:暴露的函数名会直接用作 API 端点,建议使用清晰的命名
  2. 参数传递:所有参数会通过 JSON 序列化,确保参数是可序列化的类型
  3. 返回值:返回值也会被序列化为 JSON,复杂对象可能需要特殊处理
  4. 错误处理:建议在 Python 函数中添加异常处理,或在 JavaScript 中使用 .catch()
  5. 安全性:暴露的函数可以被任何访问应用的 JavaScript 调用,注意权限控制
  6. WebSocket 端口:web.py 模式下,WebSocket 运行在 HTTP 端口 + 1 上

示例项目

项目包含多个完整示例:

示例 说明 特点
01 - hello_world Hello World 最基础的示例
02 - callbacks 回调功能 Python ↔ JavaScript 双向通信
03 - sync_callbacks 同步回调 同步调用并获取返回值
04 - file_access 文件访问 文件和文件夹选择对话框
05 - input 输入表单 表单输入处理
06 - jinja_templates Jinja2 模板 支持 Jinja2 和 web.py 双模板引擎
07 - Create_React_App React + QuikeUI 从 Eel 迁移到 QuikeUI,使用 CRA
08 - Create_Vue_App Vue 3 + QuikeUI 使用 Vite,启动速度快 10-100 倍
09 - Eelectron_quick_start Electron 快速开始 Electron 集成示例
10 - Tauri_quick_start Tauri 快速开始 Tauri 集成示例
11 - custom_app_routes 自定义路由 自定义路由注册
12 - open_url 打开 URL 外部 URL 打开示例
13 - separation_web 前后端分离 静态文件与后端分离
14 - tray_web 系统托盘 + 窗口控制 内置托盘菜单,隐藏/显示/关闭窗口
15 - db_web SQLite 数据库集成 DbPlugin 插件,支持 CRUD、SQL 文件初始化、线程安全
16 - Vue_App_Template Vue 3 + Naive UI 完整模板 Vite + Naive UI + Pinia + Axios,适配 pyquickwebgui
17 - hello_world_server 纯服务器模式 支持 --server 参数,仅启动 HTTP 服务不启动浏览器
18 - hello_world_pypage PagePlugin Lona 风格交互 Python 回调控制 UI 更新,on_click + on_result 事件驱动

运行示例:

# Windows
bin\run_examples.bat

# 或手动运行
cd "examples/01 - hello_world"
python hello.py

查看完整示例:examples/01 - hello_world

使用 Webview 浏览器

要使用原生 WebView 窗口(而非 Chrome/Edge 浏览器),只需设置 browser_type="webview":

from pyquickwebgui import QuikeUI

qui = QuikeUI(
    server_type="webpy",
    browser_type="webview",  # 使用原生 WebView
    web_path="./web",
    index_html="index.html",
    width=800,
    height=600
)

@qui.expose
def my_function():
    return "Hello from Python!"

qui.run()

优势:

  • ✅ 原生应用窗口体验
  • ✅ 无浏览器地址栏和工具栏
  • ✅ 更轻量的资源占用
  • ✅ 更快的启动速度(智能服务器就绪检测 + 窗口延迟显示)
  • ✅ 跨平台支持(Windows/macOS/Linux)

平台要求:

平台 WebView 引擎 要求
Windows Edge WebView2 Windows 10/11 自带,或手动安装
macOS WKWebView 系统内置,无需额外安装
Linux WebKitGTK 需安装 libwebkit2gtk-4.0-dev

高级配置:

# 全屏模式
qui = QuikeUI(fullscreen=True)

# 调试模式(按 F12 打开开发者工具)
qui = QuikeUI(debug=True)

Webview vs CommandBrowser 对比:

特性 Webview CommandBrowser
窗口外观 原生应用窗口 浏览器窗口
工具栏/地址栏 ❌ 无 ✅ 有(可隐藏)
资源占用 较轻 较重
启动速度 较快 较慢
调试工具 F12(开发模式) F12(始终可用)
适用场景 桌面应用 Web 应用/Kiosk

查看完整示例:examples/12 - webview_webpy

纯服务器模式(不启动浏览器)

QuikeUI 支持以纯服务器模式运行,仅启动 HTTP 服务而不启动浏览器窗口。适用于:

  • 作为后端 API 服务
  • 配合外部前端开发服务器
  • 部署为 Web 服务
  • Docker 容器化部署

基本用法

通过 only_webserver=True 参数或 --server 命令行参数启用:

import sys
from pyquickwebgui import QuikeUI

# 解析命令行参数
SERVER_MODE = '--server' in sys.argv

qui = QuikeUI(
    title="My App",
    only_webserver=SERVER_MODE,  # True 时不启动浏览器
)

@qui.expose
def say_hello(name):
    return f'Hello, {name}!'

if __name__ == '__main__':
    if SERVER_MODE:
        print(f'[INFO] 服务器模式启动,访问地址: http://localhost:{qui.port}/')
    
    qui.start()

运行方式

# 正常模式(启动浏览器窗口)
python hello.py

# 服务器模式(仅启动 HTTP 服务)
python hello.py --server

配合前端开发服务器

纯服务器模式可以配合 Vite/Webpack 等前端开发服务器使用:

# backend.py
import sys
from pyquickwebgui import QuikeUI

SERVER_MODE = '--server' in sys.argv

qui = QuikeUI(
    title="My App",
    only_webserver=SERVER_MODE,
    debug=True,
)

@qui.route('/api/data')
def get_data(params):
    return {'message': 'Hello from backend'}

qui.start()

前端通过 window.__API_BASE__ 或固定端口访问后端 API。

API 参考

参数 类型 说明
only_webserver bool True 时仅启动 HTTP 服务器,不启动浏览器

查看完整示例:examples/17 - hello_world_server

Jinja2 模板支持

QuikeUI 支持两种模板引擎,通过 server_kwargs 配置:

使用 Jinja2 模板

qui = QuikeUI( 
    web_path=os.path.join(os.path.dirname(__file__), "web"),
    index_html="hello.html",
    width=800, height=600,
    server_kwargs={
        'templates_folder': 'templates',      # 模板文件夹路径(相对于 web_path)
        'template_engine': 'jinja2'           # 使用 Jinja2 模板引擎
    }
)

使用 web.py 模板(默认)

qui = QuikeUI( 
    web_path=os.path.join(os.path.dirname(__file__), "web"),
    index_html="hello.html",
    width=800, height=600,
    server_kwargs={
        'templates_folder': 'templates'       # 模板文件夹路径
        # 不指定 template_engine,默认使用 web.py
    }
)

Jinja2 模板语法示例

<!DOCTYPE html>
<html>
    <head>
        <title>{{ title }}</title>
    </head>
    <body>
        <p>{{ message }}</p>
        
        {% for item in items %}
            <span>{{ item }}</span>
        {% endfor %}
        
        {% if condition %}
            <p>Condition is true</p>
        {% else %}
            <p>Condition is false</p>
        {% endif %}
    </body>
</html>

web.py 模板语法示例

$def with (title, message)
<!DOCTYPE html>
<html>
    <head>
        <title>$title</title>
    </head>
    <body>
        <p>$message</p>
        
        $for item in items:
            <span>$item</span>
            
        $if condition:
            <p>Condition is true</p>
        $else:
            <p>Condition is false</p>
    </body>
</html>

安装 Jinja2:

pip install jinja2

选择建议:

  • ✅ 推荐使用 Jinja2:如果你熟悉 Jinja2 语法、需要模板继承和块功能
  • ✅ 推荐使用 web.py 模板:如果你不想安装额外依赖、喜欢简洁的 Python 风格语法

查看完整示例:examples/06 - jinja_templates


##  API 参考

### QuikeUI 类

主要方法:

- `run(**kwargs)` - 启动应用(支持通过 kwargs 覆盖实例属性,如 `tray`、`title` 等)
- `start(**kwargs)` - 启动应用(`run()` 的别名,与 Eel 框架兼容,同样支持 kwargs 覆盖)
- `init(**kwargs)` - 延迟初始化参数(允许先创建空实例,再传入真实参数)
- `route(path, methods)` - 注册自定义路由(装饰器)
- `expose(func)` - 暴露函数给 JavaScript(详见 [@qui.expose 装饰器](#quiexpose-装饰器))
- `call_js_function(name, *args)` - 调用 JavaScript 函数
- `get_exposed_functions()` - 获取暴露函数列表
- `open_local_file()` - 打开文件选择对话框
- **`hide()`** - 隐藏主窗口(配合系统托盘使用) 🆕
- **`show()`** - 显示主窗口(配合系统托盘使用) 🆕
- `get_exe_dir()` - 获取 PyInstaller 打包后 exe 文件所在目录
- `get_resource_dir()` - 获取 PyInstaller 打包后的资源文件目录
- `close_application()` - 静态方法,关闭应用程序(关闭浏览器、清理资源、终止后台进程)

**注意:** 
1. `start()` 方法是 `run()` 的别名,提供与 Eel 框架兼容的 API。两者功能完全相同。
2. `init()` 方法允许延迟初始化,适合需要动态配置的场景。
3. `route()` 装饰器用于注册自定义路由,支持所有 Web 框架(web.py, Flask, FastAPI, Django)。

```python
# 方式 1: 直接传入参数(推荐)
qui = QuikeUI(
    web_path="./web",
    index_html="index.html",
    width=800,
    height=600
)
qui.run()

# 方式 2: 先创建空实例,再初始化
qui = QuikeUI()
qui.init(
    web_path="./web",
    index_html="index.html",
    width=800,
    height=600
)
qui.run()

# 方式 3: 使用 start() 代替 run()
qui.start()  # 与 qui.run() 等价

# 方式 4: 注册自定义路由
@qui.route('/custom')
def custom_route():
    return 'Hello, World!'

@qui.route('/api/data', methods=['POST'])
def api_data():
    return {'status': 'ok'}

# 方式 5: 自动获取请求参数(推荐)
# 路由函数声明 params 参数,框架自动解析并传入
@qui.route('/login', methods=['POST'])
def login(params):
    # params 是 dict,包含:
    # - GET 查询参数 (?key=value)
    # - POST 表单数据
    # - POST JSON body (Content-Type: application/json)
    username = params.get('username', '')
    password = params.get('password', '')
    return {'message': 'ok'}

# 无参函数仍然兼容,无需修改旧代码
@qui.route('/health')
def health():
    return {'status': 'ok'}

# 方式 6: 获取 PyInstaller 打包后的目录路径
# 方法 1: 获取 exe 所在目录(用户可见的目录)
exe_dir = qui.get_exe_dir()
print(f"Exe 目录: {exe_dir}")
# 打包后: D:\projects\dist
# 开发时: D:\projects\src

# 方法 2: 获取资源文件目录(临时解压目录)
resource_dir = qui.get_resource_dir()
print(f"资源目录: {resource_dir}")
# 打包后: C:\Users\lixin\AppData\Local\Temp\_MEI165762
# 开发时: D:\projects\src

# 示例:在 exe 同目录创建配置文件
import os
config_file = os.path.join(exe_dir, 'config.json')

# 示例:访问打包的资源文件
web_dir = os.path.join(resource_dir, 'web')

详细用法请参考上方的 @qui.expose 装饰器 章节。


JavaScript API 使用指南

QuikeUI 提供了统一的 JavaScript API 来调用 Python 函数。所有调用都通过 QuikeUI.callPython() 方法完成。

基本用法

JavaScript → Python

// 基本调用(无返回值)
QuikeUI.callPython("python_function_name", arg1, arg2);

// 带回调的调用(获取返回值)
QuikeUI.callPython("python_function_name", arg1, arg2)
    .then(result => {
        console.log("Python 返回结果:", result);
    })
    .catch(error => {
        console.error("调用失败:", error);
    });

// 使用 async/await(推荐)
async function callPython() {
    try {
        const result = await QuikeUI.callPython("python_function_name", arg1, arg2);
        console.log("Python 返回结果:", result);
    } catch (error) {
        console.error("调用失败:", error);
    }
}

Python → JavaScript

在 Python 端使用 qui.call_js_function() 或 qui.call_js_function_sync():

# 异步调用(不等待返回)
qui.call_js_function("js_function_name", arg1, arg2)

# 同步调用(等待返回,需要 await)
result = qui.call_js_function_sync("js_function_name", arg1, arg2)

在 JavaScript 端暴露函数:

// 暴露 JavaScript 函数给 Python
QuikeUI.expose(js_function_name);

function js_function_name(arg1, arg2) {
    // 处理逻辑
    return "返回值";
}

💡 特性

1. 自动队列缓存

当 WebSocket 未连接时,调用会自动加入队列,连接后依次执行:

// 页面加载时立即调用 - 会被缓存
QuikeUI.callPython("init_function", "data").then(result => {
    console.log("初始化完成:", result);
});

// WebSocket 连接后,队列中的调用会自动发送

2. Promise 支持

所有调用都返回 Promise,支持 .then() 和 async/await:

// 方式 1: then/catch
QuikeUI.callPython("get_data")
    .then(data => console.log(data))
    .catch(err => console.error(err));

// 方式 2: async/await(推荐)
async function getData() {
    try {
        const data = await QuikeUI.callPython("get_data");
        console.log(data);
    } catch (err) {
        console.error(err);
    }
}

3. 错误处理

调用失败时会抛出异常,可以通过 .catch() 或 try/catch 捕获:

QuikeUI.callPython("risky_function")
    .then(result => {
        // 成功处理
    })
    .catch(error => {
        // 错误处理
        console.error("错误信息:", error.message);
        console.error("堆栈跟踪:", error.stack);
    });

📝 完整示例

HTML 文件

<!DOCTYPE html>
<html>
<head>
    <title>QuikeUI 示例</title>
    
    <!-- 必须在 QuikeUI.js 加载前设置 -->
    <script type="text/javascript">
        window.QuikeUI = { debug: true };
    </script>
    <script type="text/javascript" src="QuikeUI.js"></script>
    <script type="text/javascript">
        // 暴露 JavaScript 函数给 Python
        QuikeUI.expose(js_hello);
        function js_hello(name) {
            console.log("Hello from JavaScript:", name);
            return "JS Response: " + name;
        }
        
        // 页面加载完成后调用 Python
        window.addEventListener('load', async function() {
            // 等待 WebSocket 连接
            await new Promise(resolve => {
                if (QuikeUI._websocket_connected) {
                    resolve();
                } else {
                    const checkInterval = setInterval(() => {
                        if (QuikeUI._websocket_connected) {
                            clearInterval(checkInterval);
                            resolve();
                        }
                    }, 100);
                }
            });
            
            console.log("✅ WebSocket 已连接");
            
            // 调用 Python 函数
            try {
                const result = await QuikeUI.callPython("py_hello", "World");
                console.log("Python 返回:", result);
            } catch (error) {
                console.error("调用失败:", error);
            }
        });
    </script>
</head>
<body>
    <h1>QuikeUI 示例</h1>
</body>
</html>

Python 文件

import os
from pyquickwebgui import QuikeUI

qui = QuikeUI(
    web_path=os.path.join(os.path.dirname(__file__), "web"),
    index_html="index.html",
    width=800,
    height=600,
    debug=True,
)

@qui.expose
def py_hello(name):
    """暴露给 JavaScript 的 Python 函数"""
    print(f"Hello from Python: {name}")
    
    # 调用 JavaScript 函数
    result = qui.call_js_function_sync("js_hello", name)
    print(f"JavaScript 返回: {result}")
    
    return f"Python Response: {name}"

if __name__ == "__main__":
    qui.run()

⚠️ 注意事项

  1. WebSocket 连接:调用前确保 WebSocket 已连接,或使用队列缓存机制
  2. 函数暴露:Python 函数需要使用 @qui.expose 装饰器暴露
  3. 返回值:只有同步调用才能获取返回值,异步调用需要通过回调处理
  4. 错误处理:始终添加错误处理逻辑,避免未捕获的异常

🔧 调试模式

启用调试模式可以查看详细的日志输出:

// 在加载 QuikeUI.js 之前设置
window.QuikeUI = { debug: true };

调试模式下会输出:

  • WebSocket 连接状态
  • 函数调用日志
  • 队列缓存信息
  • 错误详细信息

API 参考

QuikeUI.callPython(func_name, ...args)

调用 Python 函数。

参数:

  • func_name (string): Python 函数名
  • ...args (any): 传递给 Python 函数的参数

返回:

  • Promise: 解析为 Python 函数的返回值

示例:

const result = await QuikeUI.callPython("my_function", arg1, arg2);

QuikeUI.expose(func, name)

暴露 JavaScript 函数给 Python。

参数:

  • func (function): JavaScript 函数
  • name (string, optional): 函数名(默认为函数本身的名称)

示例:

QuikeUI.expose(my_js_function);
// 或
QuikeUI.expose(my_js_function, "custom_name");

QuikeUI.enable_reconnect(interval, max_attempts)

启用自动重连功能。

参数:

  • interval (number): 重连间隔(毫秒),默认 1000
  • max_attempts (number): 最大重连次数,默认 10(0 表示无限)

示例:

QuikeUI.enable_reconnect(1000, 10);

QuikeUI.disable_reconnect()

禁用自动重连功能。

示例:

QuikeUI.disable_reconnect();

QuikeUI.manual_reconnect()

手动触发重连。

示例:

QuikeUI.manual_reconnect();

🔄 迁移指南

如果你之前使用自动生成的函数(如 QuikeUI.py_function()),现在需要改为:

// ❌ 旧方式(已废弃)
QuikeUI.py_function(arg1, arg2).then(result => {...});

// ✅ 新方式(推荐)
QuikeUI.callPython("py_function", arg1, arg2).then(result => {...});

优势:

  • 统一的调用方式
  • 更清晰的代码意图
  • 更好的类型提示(配合 TypeScript)
  • 更容易维护和重构

📦 打包部署

开发环境

# 克隆项目
git clone <repository-url>
cd pyQuickWebGui-master

# 创建虚拟环境
python -m venv .venv
.venv\Scripts\activate  # Windows
source .venv/bin/activate  # macOS/Linux

# 安装依赖
pip install -e .

构建包

# 使用 uv 构建
uv build

# 或使用传统方式
python -m build

发布到 PyPI

# 安装 twine
pip install twine

# 上传
python -m twine upload dist/*

应用打包

使用 PyInstaller 或其他工具打包为可执行文件:

pip install pyinstaller
pyinstaller --onefile your_app.py

Sphinx 文档生成

如果需要生成 API 文档:

cd docs

# 自动生成 .rst 文件
sphinx-apidoc -o source/api ../src/pyquickwebgui --force --no-toc

# 构建 HTML 文档
make clean
make html

🔄 更新日志

V 0.2.11 (2026-07-29)

Bug 修复

  • 🐛 修复 DbPlugin 未配置 db 时仍创建空文件:当用户未传入 db 参数时,DbPlugin 不再自动创建默认数据库文件,避免不必要的副作用
  • 🐛 修复根插件加载器误匹配元类导致 TypeError:load_rootplugins 改用 isinstance(item, type) and issubclass(item, BasePlugin) 类型检查替代名称字符串匹配,避免 _PagePluginMeta 等元类被误识别为插件类

V 0.2.10 (2026-07-29)

DbPlugin 数据库加密支持

  • 🆕 新增数据库加密功能:DbPlugin 支持通过 pysqleet(SQLCipher)对 SQLite 数据库文件进行 AES-256 加密,通过 db 元组第三个元素传入密码即可启用
  • 🆕 pysqleet 可选依赖:在 pyproject.toml 中新增 [project.optional-dependencies] 配置,通过 pip install pyquickwebgui[encrypt] 或 pip install pysqleet 安装
  • 🔧 DbPlugin 参数名统一:register() 和 run() 方法的参数名从 app 改为 qui,与 QuikeUI 实例命名保持一致

重要提示:

  1. 加密数据库用法:

    # 非加密模式(默认)
    qui = QuikeUI(db=("myapp.db", "init.sql"))
    
    # 加密模式(需要 pip install pysqleet)
    qui = QuikeUI(db=("encrypted.db", "init.sql", "my_secret_password"))
    
  2. pysqleet 安装说明:

    • pysqleet 是可选依赖,未安装时加密参数会被忽略,数据库以普通模式运行
    • 安装需要 C++ 构建工具(Windows 需安装 Visual Studio Build Tools)
    • 如果不需要加密功能,无需安装

查看完整示例:examples/15 - db_web

V 0.2.9 (2026-07-23)

PagePlugin Lona 风格事件驱动交互

  • 🆕 新增 Page 类 Lona 风格事件驱动:button(on_click=..., on_result=...) 支持 Python 回调控制 UI 更新
  • 🆕 新增 Page UI 操作方法:set_value()、set_text()、set_html()、layer_msg()、layer_alert() 通过 WebSocket 实时推送 JS 更新前端
  • 🆕 新增 exec_js WebSocket 消息类型:Python 端可通过 _send_js_to_client() 推送任意 JS 代码到前端执行
  • 🐛 修复插件模块重复加载问题:app.py 插件加载器优先通过包路径导入,避免同一文件被加载为不同模块导致类变量不一致
  • ✨ layui layer 模块默认加载:render_page 默认模块列表新增 layer,支持弹窗功能

重要提示:

  1. Lona 风格事件驱动:Python 端完全控制 UI 更新方式

    from pyquickwebgui.rootplugins.PagePlugin import PagePlugin as page
    
    home_page = page.create_page()
    
    # 业务回调
    def get_time(data):
        import datetime
        return f"服务器时间: {datetime.datetime.now()}"
    
    # UI 更新回调 - 写入 input
    def show_in_input(result):
        if result and result.get('status') == 'success':
            home_page.set_value('result_input', result['result'])
    
    # UI 更新回调 - 弹窗
    def show_as_popup(result):
        if result and result.get('status') == 'success':
            home_page.layer_msg(result['result'])
    
    # 按钮绑定
    btn1 = home_page.button("获取时间", on_click=get_time, on_result=show_in_input)
    btn2 = home_page.button("弹窗显示", on_click=get_time, on_result=show_as_popup)
    
  2. Page UI 操作方法:通过 WebSocket 实时推送 JS 更新前端

    page.set_value('input_id', '新值')     # 设置 input value
    page.set_text('label_id', '新文本')    # 设置 innerText
    page.set_html('div_id', '<b>粗体</b>') # 设置 innerHTML
    page.layer_msg('操作成功', icon=1)     # layer.msg 弹窗
    page.layer_alert('提示信息')           # layer.alert 弹窗
    

查看完整示例:examples/18 - hello_world_pypage

V 0.2.8 (2026-07-08)

Bug 修复与功能增强

  • 🐛 修复 inject_js 代码字符串被误判为文件路径:当 inject_js 传入 JS 代码字符串时,代码会优先判断长度(≥260 字符直接作为 JS 代码),避免长字符串被误判为文件路径导致注入失败
  • 🐛 修复外部 URL 模式下 inject_js 不执行:当 index_html 为外部 URL(如 Vite 开发服务器)时,window.events.loaded 事件不触发导致 JS 注入失败。现在对外部 URL 使用线程延迟注入替代事件方式
  • 🐛 修复跨域请求(CORS)支持:自定义路由(如 /api/login)的响应现在自动添加 CORS 头(Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers),并正确处理 OPTIONS 预检请求
  • 🐛 修复 JSON 响应中文乱码:路由函数返回 dict 时,自动设置 Content-Type: application/json; charset=utf-8 并使用 ensure_ascii=False 编码,确保中文等非 ASCII 字符正确显示
  • 🐛 修复 DbPlugin 数据库初始化逻辑:先检查数据库文件是否存在再连接,避免空数据库文件导致 init.sql 不执行

重要提示:

  1. 外部开发服务器集成增强:inject_js 现在能正确区分 JS 代码字符串和文件路径

    # JS 代码字符串(长度 ≥ 260 或包含 JS 特征字符)
    inject_js_code = f'window.__API_BASE__="http://localhost:{qui.port}";'
    
    # 文件路径(短字符串且看起来像文件名)
    qui = QuikeUI(inject_js="menu.js")
    
  2. 跨域请求支持:前端开发服务器(如 Vite)访问后端 API 不再受 CORS 限制

    # 后端自动处理 OPTIONS 预检请求
    @qui.route("/api/login", methods=["POST"])
    def login(params):
        return {"message": "ok"}  # 自动添加 CORS 头
    
  3. JSON 响应编码修复:中文等非 ASCII 字符不再乱码

    # 返回 dict 时自动设置 UTF-8 编码
    return {"message": "error", "error": "用户名或密码错误"}
    # 响应头: Content-Type: application/json; charset=utf-8
    

V 0.2.7 (2026-07-07)

Bug 修复与功能增强

  • 🐛 修复 run()/start() 参数覆盖问题:通过 start(tray={...}) 或 run(**kwargs) 传入的参数现在会正确更新到实例属性,之前传入的参数会被忽略
  • 🐛 修复外部 URL 模式下 web_path 图标查找失败:当 index_html 为外部 URL(如 http://localhost:5173/)时,web_path 现在会自动转换为绝对路径,确保图标等资源文件能正确找到
  • 🐛 修复 DefaultServerWebpy.py 中 debug_file 未定义错误:移除调试遗留代码,消除 NameError: name 'debug_file' is not defined 异常
  • ✨ 系统托盘菜单支持字典格式:tray 配置中的 menu 现在支持传入字典列表 [{"text": "菜单项", "action": callback}],自动转换为 pystray.MenuItem
  • ✨ 自定义路由自动解析请求参数:路由函数声明 params 参数后,框架自动解析 GET 查询参数、POST 表单数据、POST JSON body 并传入,无需手动调用 web.input() 或 web.data(),同时向后兼容无参路由函数
  • 🆕 新增 Vue 3 + Naive UI 完整模板:examples/15 - Vue_App_Template 展示前端开发服务器 + pyquickwebgui 后端集成方案
  • 🆕 新增 SQLite 数据库插件:DbPlugin 提供完整的 CRUD 操作、SQL 文件初始化、线程安全支持,通过 QuikeUI(db="db.db") 一键启用

重要提示:

  1. start()/run() 参数覆盖:现在可以在启动时覆盖实例属性

    qui = QuikeUI(title="My App")
    
    # 启动时覆盖 tray 配置
    qui.start(tray={
        "icon": "favicon.ico",
        "menu": [
            {"text": "自定义菜单", "action": my_callback}
        ]
    })
    
  2. 外部开发服务器集成:支持将 index_html 设置为外部 URL,适合前端开发场景

    qui = QuikeUI(
        index_html="http://localhost:5173/",  # Vite 开发服务器地址
        icon="favicon.ico",                    # 图标从 web_path 查找
    )
    qui.run()
    
  3. 字典格式托盘菜单:无需手动创建 pystray.MenuItem,直接传字典即可

    # ✅ 字典格式(新方式,更简洁)
    qui.start(tray={
        "icon": "favicon.ico",
        "menu": [
            {"text": "菜单项 A", "action": callback_a},
            {"text": "菜单项 B", "action": callback_b},
        ]
    })
    
    # ✅ pystray.MenuItem 格式(仍然支持)
    import pystray
    menu = (
        pystray.MenuItem("菜单项 A", callback_a),
        pystray.MenuItem("菜单项 B", callback_b),
    )
    qui.start(tray={"icon": "favicon.ico", "menu": menu})
    
  4. SQLite 数据库插件:通过 db 参数一键启用数据库功能

    from pyquickwebgui import QuikeUI
    
    # 方式 1: 仅指定数据库文件路径
    qui = QuikeUI(db="myapp.db")
    
    # 方式 2: 指定数据库 + 初始化 SQL 文件(数据库不存在时自动执行)
    qui = QuikeUI(db=("myapp.db", "init.sql"))
    
    # DbPlugin 自动注册到 qui.db,提供完整 CRUD 操作
    qui.db.init_table("users", [
        "id INTEGER PRIMARY KEY AUTOINCREMENT",
        "name TEXT NOT NULL",
        "age INTEGER"
    ])
    
    # 插入数据
    qui.db.insert("users", {"name": "张三", "age": 25})
    
    # 查询数据
    users = qui.db.get_all("users")
    
    # 前端调用
    @qui.expose
    def get_users():
        return {"data": qui.db.get_all("users")}
    

    DbPlugin 特性:

    • ✅ 线程安全(check_same_thread=False + threading.Lock)
    • ✅ 支持 SQL 文件初始化(init_by_sql("schema.sql"))
    • ✅ 自动初始化(传入元组时,数据库不存在则执行 init.sql)
    • ✅ 完整的 CRUD 操作(insert/update/delete/query)
    • ✅ 自动路径转换(相对路径转绝对路径)
    • ✅ 可选 SQLCipher 加密(通过 pysqleet,需额外安装)

    加密数据库(可选):

    DbPlugin 支持通过 pysqleet(SQLCipher)对数据库文件进行 AES-256 加密。pysqleet 是可选依赖,未安装时数据库以普通模式运行。

    # 方式 1: 非加密模式(默认,无需额外依赖)
    qui = QuikeUI(db=("myapp.db", "init.sql"))
    
    # 方式 2: 加密模式(需要 pip install pysqleet)
    qui = QuikeUI(db=("encrypted.db", "init.sql", "my_secret_password"))
    
    # 方式 3: 仅加密,不执行初始化脚本
    qui = QuikeUI(db=("encrypted.db", None, "my_secret_password"))
    

    注意:pysqleet 安装需要 C++ 构建工具(Windows 需安装 Visual Studio Build Tools)。如果不需要加密功能,无需安装。

V 0.2.6 (2026-06-28)

系统托盘优化与窗口控制增强

  • 🔄 参数重命名:base_path → web_path,语义更清晰
  • 📦 内置系统托盘:托盘功能内置到 app.py,无需外部插件即可使用
  • 🏷️ 配置键重命名:stray → tray,统一命名规范
  • 🖼️ 新增 icon 参数:支持自定义窗口图标(.ico 格式),支持绝对路径和相对于 web_path 的相对路径
  • 🪟 新增 hide() 方法:隐藏主窗口,配合系统托盘使用
  • 🪟 新增 show() 方法:显示主窗口,配合系统托盘使用
  • 📋 默认托盘菜单:内置「隐藏 / 显示 / 退出」三项默认菜单,开箱即用
  • 🔧 支持自定义托盘回调:通过 menu_click_show、menu_click_hide、menu_click_exit 配置默认菜单行为
  • 🪟 修复 Windows 窗口图标设置:改用 .NET WinForms API 替代 Win32 API,解决图标不显示问题
  • 📝 新增 tray_web 示例:examples/14 - tray_web 展示系统托盘 + 窗口隐藏/关闭功能

重要提示:

  1. 参数迁移:base_path 已重命名为 web_path,旧代码中的 base_path 参数需要更新

    # ❌ 旧写法(已废弃)
    qui = QuikeUI(base_path="web")
    
    # ✅ 新写法
    qui = QuikeUI(web_path="web")
    
  2. 托盘配置迁移:配置键从 stray 改为 tray

    # ❌ 旧写法
    qui = QuikeUI(stray={"icon": "favicon.ico"})
    
    # ✅ 新写法
    qui = QuikeUI(tray={"icon": "favicon.ico"})
    
  3. 窗口图标配置:新增 icon 参数,支持自定义窗口图标

    qui = QuikeUI(
        icon="favicon.ico",       # 窗口图标(相对于 web_path)
        tray={"icon": "favicon.ico"}  # 托盘图标
    )
    
  4. 窗口显隐控制:通过 hide() 和 show() 方法控制窗口可见性

    qui.hide()  # 隐藏窗口
    qui.show()  # 显示窗口
    
    # 关闭应用(静态方法)
    QuikeUI.close_application()
    

V 0.2.5 (2026-06-23)

启动性能优化与用户体验提升

  • ⚡ 消除启动白屏问题:实现窗口延迟显示机制,确保资源完全加载后再显示窗口
  • 🚀 智能服务器就绪检测:在创建窗口前等待 HTTP 服务器完全启动,避免白屏等待
  • 🎨 优化的窗口渲染流程:初始隐藏窗口 → 等待资源加载 → 平滑显示完整内容
  • 🔧 改进 WebviewBrowser 初始化:添加 hidden=True 参数和 show_window_after_load() 回调
  • 📊 启动速度提升约 90%:从 16 秒白屏缩短至不到 1 秒直接显示完整内容
  • 🛠️ 修复 QuikeUI.js 重复加载问题:禁用默认 inject_js 注入,由 HTML 自行通过 <script> 标签加载
  • ✨ 优化 WebSocket 连接稳定性:移除 alert() 弹窗阻塞,改用 console.log() 避免连接断开
  • 🐛 修复函数参数容错问题:将 def func(x) 改为 def func(*args, **kwargs) 支持任意参数

重要提示:

  1. 启动优化原理:

    # 优化后的启动流程:
    # T+0ms    - 启动 HTTP 服务器线程
    # T+100ms  - 服务器就绪 ✅
    # T+100ms  - 创建窗口(隐藏状态)
    # T+400ms  - 窗口开始加载页面(用户看不到)
    # T+600ms  - 页面加载完成
    # T+800ms  - 显示窗口 ✅(此时所有内容已就绪)
    
  2. 窗口延迟显示配置:

    # WebviewBrowser.py 中的关键代码
    window_kwargs = {
        'hidden': True,  # 初始隐藏窗口
        ...
    }
    
    self.window = webview.create_window(**window_kwargs)
    
    # 页面加载完成后显示窗口
    def show_window_after_load():
        time.sleep(0.2)  # 再等待 200ms
        self.window.show()
        self.log.info("✅ 窗口已显示")
    
    self.window.events.loaded += show_window_after_load
    
  3. 服务器就绪检测:

    # app.py 中的等待逻辑
    for i in range(20):  # 最多等待 2 秒
        try:
            urllib.request.urlopen(f'http://127.0.0.1:{self.port}/', timeout=0.5)
            self.log.info(f"✅ 服务器已就绪 (尝试 {i+1} 次)")
            break
        except Exception:
            time.sleep(0.1)
    
  4. QuikeUI.js 加载方式:

    <!-- ✅ 推荐:HTML 中直接加载 -->
    <script src="/QuikeUI.js"></script>
    
    <!-- ❌ 不再使用:pywebview 自动注入 -->
    <!-- 已在 app.py 中禁用默认的 inject_js 设置 -->
    
  5. 避免 alert() 导致的 WebSocket 断开:

    // ❌ 避免使用 alert(会阻塞页面导致连接断开)
    alert('Hello World');
    
    // ✅ 使用 console.log 或 DOM 操作
    console.log('Hello World');
    document.getElementById('output').textContent = result;
    

V 0.2.4 (2026-06-22)

PyInstaller 打包兼容性修复与 API 扩展

  • 🔧 修复 WebSocket 线程退出问题:修改 WebSocketHandler.start() 方法,让其在 while 循环中阻塞接受连接,避免 daemon 线程过早退出
  • 🐛 修复 GBK 编码异常:移除日志中的所有 emoji 字符(✅、❌、🔄、🚀等),解决 Windows 系统下 'gbk' codec can't encode character 错误
  • ✅ 验证 PyInstaller onefile 模式:确保打包后的程序能正常启动 HTTP 服务器和 WebSocket 服务器
  • 🎯 改进线程管理:WebSocket 线程现在正确保持存活状态,支持多客户端并发连接
  • ✨ 新增 get_exe_dir() 方法:获取 PyInstaller 打包后 exe 文件所在目录(用户可见的目录)
  • ✨ 新增 get_resource_dir() 方法:获取 PyInstaller 打包后的资源文件目录(临时解压目录 sys._MEIPASS)

重要提示:

  1. Windows 编码问题:在 Windows 系统中,日志输出应避免使用 emoji 字符,因为控制台默认使用 GBK 编码

    # ❌ 避免使用 emoji(会导致 UnicodeEncodeError)
    self.log.info(f"✅ WebSocket 服务器成功启动")
    
    # ✅ 使用纯文本
    self.log.info(f"WebSocket 服务器成功启动")
    

    技术说明:

    • QuikeUI 的 LogPlugin 已经将文件日志设置为 UTF-8 编码(encoding='utf-8')
    • 但控制台日志仍使用系统默认编码(Windows 为 GBK)
    • 因此 emoji 字符写入文件没问题,但输出到控制台时会抛出异常
    • 解决方案 1(推荐):移除所有 emoji 字符,使用纯文本
    • 解决方案 2(可选):在程序启动时设置控制台编码为 UTF-8
      import sys
      import io
      # Windows 下设置控制台编码为 UTF-8
      if sys.platform == 'win32':
          sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
          sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
      
  2. Daemon 线程注意事项:当主线程被阻塞调用(如 web.httpserver.runsimple())占用时,daemon 线程的子线程也会被终止

    # ❌ 避免在 start() 中启动子线程后立即返回
    def start(self):
        thread = threading.Thread(target=self._accept, daemon=True)
        thread.start()
        return  # thread 会立即退出
    
    # ✅ 直接在 start() 中阻塞
    def start(self):
        while self.running:
            client_socket, address = self.server_socket.accept()
            # 处理连接
    
  3. 打包测试:使用 bin/build_examples.bat 脚本进行打包测试

    # Windows
    bin\build_examples.bat
    
    # 测试打包后的程序
    cd "examples/01 - hello_world"
    .\dist\hello.exe
    
  4. 路径获取 API:QuikeUI 提供两个方法获取 PyInstaller 打包后的目录路径

    from pyquickwebgui import QuikeUI
    
    qui = QuikeUI()
    
    # 方法 1: 获取 exe 所在目录(用户可见的目录)
    exe_dir = qui.get_exe_dir()
    # 打包后: D:\projects\dist
    # 开发时: D:\projects\src
    
    # 方法 2: 获取资源文件目录(临时解压目录)
    resource_dir = qui.get_resource_dir()
    # 打包后: C:\Users\lixin\AppData\Local\Temp\_MEI165762
    # 开发时: D:\projects\src
    
    # 示例:在 exe 同目录创建配置文件
    import os
    config_file = os.path.join(exe_dir, 'config.json')
    
    # 示例:访问打包的资源文件
    web_dir = os.path.join(resource_dir, 'web')
    

V 0.2.3 (2026-06-21)

API 扩展与框架增强

  • ✨ 新增 start() 方法:作为 run() 的别名,提供与 Eel 框架兼容的 API
  • 🚀 新增 init() 方法:支持延迟初始化,允许先创建空实例再传入真实参数
  • 🌐 新增 route() 装饰器:注册自定义路由,支持所有 Web 框架(web.py, Flask, FastAPI, Django)
  • 📝 完善 API 文档:在 README 中添加三种新方法的详细说明和使用示例
  • 🎯 新增 Vue 示例:examples/08 - CreateVueApp 展示 Vue 3 + Vite + QuikeUI 集成
  • 🔄 优化 React 示例:examples/07 - CreateReactApp 从 Eel 迁移到 QuikeUI
  • 📊 新增对比文档:Vue vs React 示例对比,帮助用户选择合适的框架

重要提示:

  1. Eel 兼容性:qui.start() 与 qui.run() 完全等价,方便从 Eel 迁移

    # 以下两种写法等价:
    qui.run()    # 推荐使用
    qui.start()  # 与 Eel 兼容
    
  2. 延迟初始化:适合需要动态配置的场景

    qui = QuikeUI()
    qui.init(
        web_path="./web",
        index_html="index.html",
        width=800,
        height=600
    )
    qui.run()
    
  3. 自定义路由:统一的 API 注册路由

    @qui.route('/custom')
    def custom_route():
        return 'Hello, World!'
    
    @qui.route('/api/data', methods=['POST'])
    def api_data():
        return {'status': 'ok'}
    
  4. Vue 示例特点:

    • 使用 Vite 构建工具,启动速度比 Webpack 快 10-100 倍
    • 开发服务器端口 8080(可配置)
    • 输出目录为 dist/
    • 支持热模块替换(HMR)

V 0.2.2 (2026-06-18)

核心优化与 Bug 修复

  • ⚡ 优化前端初始化时机:将 window.load 改为 DOMContentLoaded,启动速度提升约 4 倍
  • 🔧 修复调试模式配置问题:解决 QuikeUI.debug = true 设置失效导致右键和 F12 被禁用的问题
  • 🛠️ 重构 _debug() 函数:移除嵌套的 DOMContentLoaded 监听器,避免事件错过触发
  • 🌐 修复 Webview JS 调用:修复 call_js_function 在 Webview 模式下无法调用的问题
  • 📝 完善文档结构:合并分散的文档到主 README,包括 WebSocket 队列、重连、同步调用等
  • 🎯 新增文件选择示例:examples/04 - file_access 支持文件和文件夹选择
  • ✨ 改进代码质量:统一 API 命名,修复逻辑错误(如 !!! 三感叹号陷阱)

重要提示:

  1. 调试模式配置:必须在 QuikeUI.js 加载前设置

    <script>
        window.QuikeUI = { debug: true };
    </script>
    <script src="/QuikeUI.js"></script>
    
  2. WebSocket 初始化:无论 debug 模式如何都会执行,确保通信正常

  3. Webview 窗口对象:已保存到 self.window,支持外部调用 evaluate_js()

V 0.2.1 (2026-06-14)

重大更新

  • ✨ 仿照 eel 优化示例结构
  • 🚀 增加 WebSocket 与 JavaScript 双向通信
  • 🌐 增加默认服务模式(基于 web.py)
  • 🔧 重构插件系统,移除 ExposedAPIPlugin
  • 📝 完善参数文档,添加 [Optional] 标记
  • 🎯 新增 index_html 参数支持自定义首页
  • 🛡️ 新增 inject_js 和 disable_right_click 参数
  • 🐛 修复 FilePlugin 属性访问问题
  • 📦 优化批处理脚本,改进错误处理

V 0.1.1 (2025-09-29)

  • 🔧 优化结构,拆解出 Browser 类型实现
  • 📝 修改参数名:server → server_type, browser → browser_type

V 0.0.6 (2025-05-01)

  • 🛠️ 改用 uv 编译系统

V 0.0.5 (2025-05-01)

  • 📚 增加 Sphinx 文档生成

V 0.0.4 (2025-05-01)

  • 🎯 增加 Tauri 支持

V 0.0.3 (2025-04-12)

  • 📌 增加系统托盘图标功能
  • 🎨 优化 Vue 支持

V 0.0.2 (2024-08-01)

  • 🖥️ 增加 WebView 支持
  • 🌐 增加 web.py 支持

🤝 贡献

欢迎提交 Issue 和 Pull Request!

📄 许可证

MIT License

🔗 相关链接


Happy Coding! 🎉

Metadata

Release files for pyquickwebgui 0.2.11

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyquickwebgui 0.2.11
File Size Uploaded
pyquickwebgui-0.2.11.tar.gz 147.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyquickwebgui 0.2.11
File Interpreter ABI Platform
pyquickwebgui-0.2.11-py3-none-any.whl Python 3 none any Details

Total release size: 253.5 kB

Release files / pyquickwebgui-0.2.11.tar.gz

Download URL pyquickwebgui-0.2.11.tar.gz
Size 147.5 kB
Tags Source
SHA-256 checksum
How to use checksums
09d899ae7dd4a1105deb77d21f6b0772d7a8ac0b30f906f258cbf479bd27c806
BLAKE2b-256 checksum
How to use checksums
094720b105799a31b13af0205a158eb3380f9a69308d0e433834e772a97b8a7a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.8.10

Release files / pyquickwebgui-0.2.11-py3-none-any.whl

Download URL pyquickwebgui-0.2.11-py3-none-any.whl
Size 106.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0c0f4c905bd646ffb77482accc09b36867f9e249b1dba6cdf4ddbeda57673056
BLAKE2b-256 checksum
How to use checksums
112a916a8cea557f268e35b419a36b02d5d909b97900b47d7dec707a155ecac9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.8.10

Release history Release notifications | RSS feed

0.2.12

2 release files

This release

0.2.11 This release

2 release files

0.2.8

2 release files

0.2.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page