# pyquickwebgui
**Repository Path**: iiixxxiii/pyquickwebgui
## Basic Information
- **Project Name**: pyquickwebgui
- **Description**: python使用webui制作桌面应用
- **Primary Language**: Python
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2026-06-13
- **Last Updated**: 2026-07-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# py-quick-webgui
最新版本: v0.2.7
[](https://www.python.org/)
[](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 开发服务器),方便前后端分离开发
## 📋 目录
- [安装](#-安装)
- [快速开始](#-快速开始)
- [核心功能](#-核心功能)
- [示例项目](#-示例项目)
- [API 参考](#-api-参考)
- [打包部署](#-打包部署)
- [更新日志](#-更新日志)
## 📦 安装
### 基础安装
```bash
pip install pyquickwebgui
```
### 完整依赖
根据不同需求安装额外依赖:
```bash
# 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
# Tauri 支持
# 请参考 Tauri 官方文档
```
## 🚀 快速开始
### 1. Hello World 示例
创建 `hello.py`:
```python
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`:
```html
Hello World
Hello from QuikeUI!
```
运行:
```bash
python hello.py
```
### 2. Flask 示例
```python
from flask import Flask
from pyquickwebgui import QuikeUI
app = Flask(__name__)
@app.route('/')
def home():
return 'Hello Flask!
'
qui = QuikeUI(
app=app,
server_type="flask",
width=800,
height=600
)
qui.run()
```
### 3. FastAPI 示例
```python
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(函数调用):
```json
{"call": 1234567890, "name": "function_name", "args": ["arg1", "arg2"]}
```
- Python → JavaScript(返回结果):
```json
{"return": 1234567890, "status": "ok", "value": "result_data"}
```
- Python → JavaScript(发送函数列表):
```json
{"type": "py_functions", "functions": ["func1", "func2"]}
```
#### Python 调用 JavaScript
QuikeUI 支持两种浏览器类型的 JS 调用:
**1. WebView 模式**
```python
# webview 直接执行 JS
qui.call_js_function('jsFunction', 'arg1', 'arg2')
```
**2. CommandBrowser 模式(通过 WebSocket)**
```python
# 方式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 可能尚未建立的问题。
```javascript
// ✅ 即使 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 发送。
```javascript
// 内部实现示例
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 函数并等待返回值:
```python
# 同步调用 JS 函数(阻塞直到收到响应)
result = qui.call_js_function_sync("js_random", timeout=5.0)
print(f'从 JavaScript 获取到: {result}')
```
**重要注意事项:**
1. **WebSocket 连接时机**
同步调用必须在 **WebSocket 连接建立之后** 才能执行:
```python
# ❌ 错误:在 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. **异常处理**
```python
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 自动重连功能,确保在网络波动或服务器重启时应用能够自动恢复连接。
```javascript
// 默认启用,每2秒重试,最多10次
// 可自定义配置
QuikeUI.enable_reconnect(
interval = 3000, // 重连间隔(毫秒)
max_attempts = 5 // 最大重连次数(0=无限)
);
// 禁用自动重连
QuikeUI.disable_reconnect();
// 手动触发重连
QuikeUI.manual_reconnect();
```
**重连特性:**
- ✅ 连接断开后自动尝试重连
- ✅ 可配置重连间隔和最大次数
- ✅ 重连成功后自动刷新队列
- ✅ 达到最大次数后拒绝待处理 Promise
- ✅ 支持手动控制和禁用
- ✅ 详细的日志输出和告警提示
**工作流程:**
```
连接断开 (onclose)
↓
检查是否启用自动重连
├─ 否 → 结束
└─ 是 ↓
检查是否达到最大次数
├─ 是 → 拒绝所有待处理 Promise,显示告警
└─ 否 ↓
增加重连计数
↓
延迟指定时间
↓
关闭旧连接(如果存在)
↓
创建新 WebSocket 连接
↓
连接成功 → 重置计数,刷新队列
连接失败 → 再次触发 onclose,循环
```
**使用场景:**
1. **网络不稳定环境**
```javascript
QuikeUI.enable_reconnect(5000, 20); // 每5秒重试,最多20次
```
2. **服务器维护期间**
```javascript
QuikeUI.enable_reconnect(3000, 0); // 无限重连
```
3. **用户登出时**
```javascript
function logout() {
QuikeUI.disable_reconnect(); // 禁用重连
QuikeUI.callPython('logout').then(() => {
window.location.href = '/login';
});
}
```
**告警功能:**
当达到最大重连次数时,系统会提供多层告警:
1. **控制台告警**:详细的错误信息和排查建议
2. **浏览器通知**:如果用户授予权限,显示系统通知
3. **自定义事件**:监听 `qui_reconnect_failed` 事件进行自定义处理
4. **Promise 拒绝**:所有待处理的 Promise 会被拒绝,带有详细错误信息
```javascript
// 监听重连失败事件
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)
- 避免频繁的手动重连
- 监控重连次数,及时发现网络问题
```javascript
// 查看重连状态
console.log('重连启用:', QuikeUI._reconnect_enabled);
console.log('当前次数:', QuikeUI._reconnect_attempts);
console.log('连接状态:', QuikeUI._websocket_connected);
```
### 参数配置
QuikeUI 支持丰富的配置选项:
```python
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` 自动转换为绝对路径(用于图标等资源查找)
```python
# 方式 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` 目录下查找文件:
```python
# 方式 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. 绝对路径
直接使用完整的文件路径:
```python
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 代码:
```python
qui = QuikeUI(
inject_js="console.log('Hello from Python!');"
)
# 多行代码
qui = QuikeUI(
inject_js="""
console.log('初始化完成');
window.customConfig = { debug: true };
"""
)
```
**注意事项:**
- ✅ 如果 HTML 中已通过 `
```
#### 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(当前版本暂不支持)
**示例:**
```javascript
// 无参数调用
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
**依赖安装:**
```bash
pip install flask-socketio eventlet
```
**特点:**
- 使用 `flask-socketio` 实现 WebSocket
- 自动降级到普通 Flask 服务器(如果不支持 WebSocket)
- 不支持 `waitress`(因为它不支持 WebSocket)
#### FastAPI
**依赖安装:**
```bash
pip install uvicorn
```
**特点:**
- 原生支持 WebSocket
- 支持异步函数(`async def`)
- 性能更好
#### web.py
**依赖安装:**
```bash
# 无需额外依赖,使用内置 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):**
```javascript
fetch('/api/exposed/say_hello', {
method: 'POST',
body: JSON.stringify({args: ['World']})
})
```
**现在(WebSocket):**
```javascript
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 服务不启动浏览器 |
运行示例:
```bash
# Windows
bin\run_examples.bat
# 或手动运行
cd "examples/01 - hello_world"
python hello.py
```
### 使用 Webview 浏览器
要使用原生 WebView 窗口(而非 Chrome/Edge 浏览器),只需设置 `browser_type="webview"`:
```python
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` |
**高级配置:**
```python
# 全屏模式
qui = QuikeUI(fullscreen=True)
# 调试模式(按 F12 打开开发者工具)
qui = QuikeUI(debug=True)
```
**Webview vs CommandBrowser 对比:**
| 特性 | Webview | CommandBrowser |
|------|---------|----------------|
| 窗口外观 | 原生应用窗口 | 浏览器窗口 |
| 工具栏/地址栏 | ❌ 无 | ✅ 有(可隐藏) |
| 资源占用 | 较轻 | 较重 |
| 启动速度 | 较快 | 较慢 |
| 调试工具 | F12(开发模式) | F12(始终可用) |
| 适用场景 | 桌面应用 | Web 应用/Kiosk |
查看完整示例:[examples/12 - webview_webpy](examples/12%20-%20webview_webpy/)
### 纯服务器模式(不启动浏览器)
QuikeUI 支持以纯服务器模式运行,仅启动 HTTP 服务而不启动浏览器窗口。适用于:
- 作为后端 API 服务
- 配合外部前端开发服务器
- 部署为 Web 服务
- Docker 容器化部署
#### 基本用法
通过 `only_webserver=True` 参数或 `--server` 命令行参数启用:
```python
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()
```
#### 运行方式
```bash
# 正常模式(启动浏览器窗口)
python hello.py
# 服务器模式(仅启动 HTTP 服务)
python hello.py --server
```
#### 配合前端开发服务器
纯服务器模式可以配合 Vite/Webpack 等前端开发服务器使用:
```python
# 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](examples/17%20-%20hello_world_server/)
### Jinja2 模板支持
QuikeUI 支持两种模板引擎,通过 `server_kwargs` 配置:
#### 使用 Jinja2 模板
```python
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 模板(默认)
```python
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 模板语法示例
```html
{{ title }}
{{ message }}
{% for item in items %}
{{ item }}
{% endfor %}
{% if condition %}
Condition is true
{% else %}
Condition is false
{% endif %}
```
#### web.py 模板语法示例
```python
$def with (title, message)
$title
$message
$for item in items:
$item
$if condition:
Condition is true
$else:
Condition is false
```
**安装 Jinja2:**
```bash
pip install jinja2
```
**选择建议:**
- ✅ **推荐使用 Jinja2**:如果你熟悉 Jinja2 语法、需要模板继承和块功能
- ✅ **推荐使用 web.py 模板**:如果你不想安装额外依赖、喜欢简洁的 Python 风格语法
查看完整示例:[examples/06 - jinja_templates](examples/06%20-%20jinja_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 装饰器](#quiexpose-装饰器) 章节。
---
## JavaScript API 使用指南
QuikeUI 提供了统一的 JavaScript API 来调用 Python 函数。所有调用都通过 `QuikeUI.callPython()` 方法完成。
### 基本用法
#### JavaScript → Python
```javascript
// 基本调用(无返回值)
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()`:
```python
# 异步调用(不等待返回)
qui.call_js_function("js_function_name", arg1, arg2)
# 同步调用(等待返回,需要 await)
result = qui.call_js_function_sync("js_function_name", arg1, arg2)
```
在 JavaScript 端暴露函数:
```javascript
// 暴露 JavaScript 函数给 Python
QuikeUI.expose(js_function_name);
function js_function_name(arg1, arg2) {
// 处理逻辑
return "返回值";
}
```
### 💡 特性
#### 1. 自动队列缓存
当 WebSocket 未连接时,调用会自动加入队列,连接后依次执行:
```javascript
// 页面加载时立即调用 - 会被缓存
QuikeUI.callPython("init_function", "data").then(result => {
console.log("初始化完成:", result);
});
// WebSocket 连接后,队列中的调用会自动发送
```
#### 2. Promise 支持
所有调用都返回 Promise,支持 `.then()` 和 `async/await`:
```javascript
// 方式 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` 捕获:
```javascript
QuikeUI.callPython("risky_function")
.then(result => {
// 成功处理
})
.catch(error => {
// 错误处理
console.error("错误信息:", error.message);
console.error("堆栈跟踪:", error.stack);
});
```
### 📝 完整示例
#### HTML 文件
```html
QuikeUI 示例
QuikeUI 示例
```
#### Python 文件
```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. **错误处理**:始终添加错误处理逻辑,避免未捕获的异常
### 🔧 调试模式
启用调试模式可以查看详细的日志输出:
```javascript
// 在加载 QuikeUI.js 之前设置
window.QuikeUI = { debug: true };
```
调试模式下会输出:
- WebSocket 连接状态
- 函数调用日志
- 队列缓存信息
- 错误详细信息
### API 参考
#### QuikeUI.callPython(func_name, ...args)
调用 Python 函数。
**参数:**
- `func_name` (string): Python 函数名
- `...args` (any): 传递给 Python 函数的参数
**返回:**
- `Promise`: 解析为 Python 函数的返回值
**示例:**
```javascript
const result = await QuikeUI.callPython("my_function", arg1, arg2);
```
#### QuikeUI.expose(func, name)
暴露 JavaScript 函数给 Python。
**参数:**
- `func` (function): JavaScript 函数
- `name` (string, optional): 函数名(默认为函数本身的名称)
**示例:**
```javascript
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 表示无限)
**示例:**
```javascript
QuikeUI.enable_reconnect(1000, 10);
```
#### QuikeUI.disable_reconnect()
禁用自动重连功能。
**示例:**
```javascript
QuikeUI.disable_reconnect();
```
#### QuikeUI.manual_reconnect()
手动触发重连。
**示例:**
```javascript
QuikeUI.manual_reconnect();
```
### 🔄 迁移指南
如果你之前使用自动生成的函数(如 `QuikeUI.py_function()`),现在需要改为:
```javascript
// ❌ 旧方式(已废弃)
QuikeUI.py_function(arg1, arg2).then(result => {...});
// ✅ 新方式(推荐)
QuikeUI.callPython("py_function", arg1, arg2).then(result => {...});
```
**优势:**
- 统一的调用方式
- 更清晰的代码意图
- 更好的类型提示(配合 TypeScript)
- 更容易维护和重构
---
## 📦 打包部署
### 开发环境
```bash
# 克隆项目
git clone
cd pyQuickWebGui-master
# 创建虚拟环境
python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # macOS/Linux
# 安装依赖
pip install -e .
```
### 构建包
```bash
# 使用 uv 构建
uv build
# 或使用传统方式
python -m build
```
### 发布到 PyPI
```bash
# 安装 twine
pip install twine
# 上传
python -m twine upload dist/*
```
### 应用打包
使用 PyInstaller 或其他工具打包为可执行文件:
```bash
pip install pyinstaller
pyinstaller --onefile your_app.py
```
### Sphinx 文档生成
如果需要生成 API 文档:
```bash
cd docs
# 自动生成 .rst 文件
sphinx-apidoc -o source/api ../src/pyquickwebgui --force --no-toc
# 构建 HTML 文档
make clean
make html
```
## 🔄 更新日志
### 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 代码字符串和文件路径
```python
# JS 代码字符串(长度 ≥ 260 或包含 JS 特征字符)
inject_js_code = f'window.__API_BASE__="http://localhost:{qui.port}";'
# 文件路径(短字符串且看起来像文件名)
qui = QuikeUI(inject_js="menu.js")
```
2. **跨域请求支持**:前端开发服务器(如 Vite)访问后端 API 不再受 CORS 限制
```python
# 后端自动处理 OPTIONS 预检请求
@qui.route("/api/login", methods=["POST"])
def login(params):
return {"message": "ok"} # 自动添加 CORS 头
```
3. **JSON 响应编码修复**:中文等非 ASCII 字符不再乱码
```python
# 返回 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()` 参数覆盖**:现在可以在启动时覆盖实例属性
```python
qui = QuikeUI(title="My App")
# 启动时覆盖 tray 配置
qui.start(tray={
"icon": "favicon.ico",
"menu": [
{"text": "自定义菜单", "action": my_callback}
]
})
```
2. **外部开发服务器集成**:支持将 `index_html` 设置为外部 URL,适合前端开发场景
```python
qui = QuikeUI(
index_html="http://localhost:5173/", # Vite 开发服务器地址
icon="favicon.ico", # 图标从 web_path 查找
)
qui.run()
```
3. **字典格式托盘菜单**:无需手动创建 `pystray.MenuItem`,直接传字典即可
```python
# ✅ 字典格式(新方式,更简洁)
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` 参数一键启用数据库功能
```python
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)
- ✅ 自动路径转换(相对路径转绝对路径)
### 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` 参数需要更新
```python
# ❌ 旧写法(已废弃)
qui = QuikeUI(base_path="web")
# ✅ 新写法
qui = QuikeUI(web_path="web")
```
2. **托盘配置迁移**:配置键从 `stray` 改为 `tray`
```python
# ❌ 旧写法
qui = QuikeUI(stray={"icon": "favicon.ico"})
# ✅ 新写法
qui = QuikeUI(tray={"icon": "favicon.ico"})
```
3. **窗口图标配置**:新增 `icon` 参数,支持自定义窗口图标
```python
qui = QuikeUI(
icon="favicon.ico", # 窗口图标(相对于 web_path)
tray={"icon": "favicon.ico"} # 托盘图标
)
```
4. **窗口显隐控制**:通过 `hide()` 和 `show()` 方法控制窗口可见性
```python
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 自行通过 `
```
5. **避免 alert() 导致的 WebSocket 断开**:
```javascript
// ❌ 避免使用 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 编码
```python
# ❌ 避免使用 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
```python
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 线程的子线程也会被终止
```python
# ❌ 避免在 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` 脚本进行打包测试
```bash
# Windows
bin\build_examples.bat
# 测试打包后的程序
cd "examples/01 - hello_world"
.\dist\hello.exe
```
4. **路径获取 API**:QuikeUI 提供两个方法获取 PyInstaller 打包后的目录路径
```python
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 迁移
```python
# 以下两种写法等价:
qui.run() # 推荐使用
qui.start() # 与 Eel 兼容
```
2. **延迟初始化**:适合需要动态配置的场景
```python
qui = QuikeUI()
qui.init(
web_path="./web",
index_html="index.html",
width=800,
height=600
)
qui.run()
```
3. **自定义路由**:统一的 API 注册路由
```python
@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` 加载前设置
```html
```
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
## 🔗 相关链接
- [参考项目: flaskwebgui](https://github.com/ClimenteA/flaskwebgui)
- [Eel - Python + JavaScript](https://github.com/ChrisKnott/Eel)
- [Tauri - 构建小型二进制文件](https://tauri.app/)
---
**Happy Coding! 🎉**