Metadata-Version: 2.1
Name: pyquickwebgui
Version: 0.2.8
Summary: 使用 Python Web 框架快速构建桌面应用程序的工具库。
Author-email: lixin <iiixxxiii@qq.com>
License: MIT
Project-URL: Homepage, https://gitee.com/iiixxxiii/pyquickwebgui
Keywords: webgui,desktop,flask,django,fastapi,webpy,pywebview
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Desktop Environment
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: psutil>=6.0
Requires-Dist: sphinx==7.1.2
Requires-Dist: sphinx-rtd-theme>=3.0.0
Requires-Dist: uvicorn>=0.33.0
Requires-Dist: waitress>=3.0.0
Requires-Dist: pystray>=0.19.5
Requires-Dist: Pillow>=9.0.0
Requires-Dist: web.py>=0.6.2
Requires-Dist: pywebview>=6.2.1
Requires-Dist: Jinja2>=3.1.6

# py-quick-webgui

最新版本: v0.2.7

[![Python Version](https://img.shields.io/badge/python-%3E%3D3.8-blue.svg)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

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

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


## ✨ 特性

- 🚀 **多框架支持**: 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
<!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>
```

运行：

```bash
python hello.py
```

### 2. Flask 示例

```python
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()
```

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

### 系统托盘

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

#### 基本用法

```python
from pyquickwebgui import QuikeUI

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

qui.run()
```

#### 默认菜单

未指定 `menu` 时，自动创建以下默认菜单：

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

#### 自定义菜单和回调

**方式 1：字典格式（推荐，更简洁）**

```python
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 格式**

```python
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 控制窗口

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

@qui.expose
def close_win():
    QuikeUI.close_application()  # 关闭应用
    return {"message": "ok"}
```

```javascript
// JavaScript 中调用
await QuikeUI.callPython('hide_win');   // 隐藏窗口
await QuikeUI.callPython('close_win');  // 关闭窗口
```

查看完整示例：[examples/14 - tray_web](examples/14%20-%20tray_web/)

### 窗口图标设置

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

1. **用户指定的 `icon` 参数**：支持绝对路径或相对于 `web_path` 的相对路径
2. **项目默认图标**：`src/pyquickwebgui/logo.ico`
3. **系统默认图标**：如果以上都不存在

```python
# 方式 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 支持插件扩展：

```python
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")
```

### @qui.expose 装饰器

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

#### Python 端用法

```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`：

```html
<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（当前版本暂不支持）

**示例：**

```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 - CreateReactApp`** | **React + QuikeUI** 🆕 | **从 Eel 迁移到 QuikeUI，使用 CRA** |
| **`08 - CreateVueApp`** | **Vue 3 + QuikeUI** 🆕 | **使用 Vite，启动速度快 10-100 倍** |
| `13 - separation_web` | 前后端分离 | 静态文件与后端分离 |
| **`14 - tray_web`** | **系统托盘 + 窗口控制** 🆕 | **内置托盘菜单，隐藏/显示/关闭窗口** |
| **`16 - Vue_App_Template`** | **Vue 3 + Naive UI 完整模板** 🆕 | **Vite + Naive UI + Pinia + Axios，适配 pyquickwebgui** |
| **`15 - db_web`** | **SQLite 数据库集成** 🆕 | **DbPlugin 插件，支持 CRUD、SQL 文件初始化、线程安全** |
| `django-desktop` | Django 代码编辑器 | Django 集成 |
| `flask-desktop` | Markdown 编辑器 | Flask + 文件操作 |
| `fastapi-desktop` | 代码编辑器 | FastAPI 集成 |
| `fastapi-vue-desktop` | Vue 编辑器 | FastAPI + Vue |
| `tauripy-flask-desktop-tray` | Tauri 集成 ⭐ | **推荐模板** |
| `webpy-desktop` | 最小化示例 | web.py 轻量级 |
| **`12 - webview_webpy`** | **Webview + Web.py** 🆕 | **原生窗口体验，无浏览器工具栏** |

运行示例：

```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/)

### 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
<!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 模板语法示例

```python
$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：**

```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
<!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 文件

```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 <repository-url>
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 自行通过 `<script>` 标签加载
- ✨ **优化 WebSocket 连接稳定性**：移除 alert() 弹窗阻塞，改用 console.log() 避免连接断开
- 🐛 **修复函数参数容错问题**：将 `def func(x)` 改为 `def func(*args, **kwargs)` 支持任意参数

**重要提示：**

1. **启动优化原理**：
   ```python
   # 优化后的启动流程：
   # T+0ms    - 启动 HTTP 服务器线程
   # T+100ms  - 服务器就绪 ✅
   # T+100ms  - 创建窗口（隐藏状态）
   # T+400ms  - 窗口开始加载页面（用户看不到）
   # T+600ms  - 页面加载完成
   # T+800ms  - 显示窗口 ✅（此时所有内容已就绪）
   ```

2. **窗口延迟显示配置**：
   ```python
   # 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. **服务器就绪检测**：
   ```python
   # 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
   <!-- ✅ 推荐：HTML 中直接加载 -->
   <script src="/QuikeUI.js"></script>
   
   <!-- ❌ 不再使用：pywebview 自动注入 -->
   <!-- 已在 app.py 中禁用默认的 inject_js 设置 -->
   ```

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
   <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

## 🔗 相关链接

- [参考项目: flaskwebgui](https://github.com/ClimenteA/flaskwebgui)
- [Eel - Python + JavaScript](https://github.com/ChrisKnott/Eel)
- [Tauri - 构建小型二进制文件](https://tauri.app/)

---

**Happy Coding! 🎉**
