1. 为什么需要pywebview?
在桌面应用开发领域,开发者常常面临一个核心矛盾:既想利用现代Web技术快速构建美观的界面,又需要访问本地系统API实现完整功能。传统方案要么像Electron那样打包整个Chromium导致体积臃肿,要么像QtWebEngine那样需要复杂绑定。这正是pywebview诞生的背景。
我最初接触pywebview是在开发一个企业内部数据分析工具时。需求很明确:前端需要复杂的数据可视化(用D3.js实现),后端需要调用本地Excel文件进行数据处理。用纯Python开发界面成本太高,而纯Web方案又无法直接操作本地文件。pywebview完美解决了这个痛点——它让我们能用HTML/CSS/JS构建界面,同时通过Python处理业务逻辑。
pywebview本质上是一个轻量级的"浏览器外壳",它使用操作系统原生Web控件(Windows用Edge/IE、macOS用WebKit、Linux用GTK的WebKit),通过Python与JavaScript双向通信桥接,实现了真正的混合开发。与Electron等方案相比,它的优势非常明显:
- 体积小巧:一个基础应用打包后仅10MB左右,而Electron应用动辄100MB起步
- 启动迅速:直接调用系统Web组件,无需加载完整浏览器引擎
- 内存友好:实测相同功能比Electron节省40%以上内存占用
- 无缝集成:Python代码可直接调用系统API,无需额外扩展
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与基础用法
2.1 安装与跨平台注意事项
安装pywebview只需要一行命令:
bash复制pip install pywebview
但不同平台需要特别注意依赖项:
- Windows:自动使用系统自带WebView2(Win10 1809+内置),建议通过winget安装最新WebView2运行时:
bash复制
winget install Microsoft.EdgeWebView2Runtime - macOS:依赖WebKit,系统自带无需处理
- Linux:需要安装GTK3和WebKitGTK,Ubuntu/Debian系执行:
bash复制sudo apt install python3-dev libwebkit2gtk-4.0-dev
踩坑提示:Linux环境下如果遇到
Gtk couldn't be initialized错误,通常是因为缺少X11服务。在无GUI的服务器上使用时,需要安装xvfb并前置执行:bash复制sudo apt install xvfb xvfb-run python your_script.py
2.2 创建第一个窗口
基础窗口创建仅需5行代码:
python复制import webview
def main():
webview.create_window('Hello World', 'https://example.com')
webview.start()
if __name__ == '__main__':
main()
但实际开发中我们更常用本地HTML文件。假设项目结构如下:
code复制my_app/
├── app.py
└── frontend/
├── index.html
└── style.css
改进后的启动代码:
python复制import webview
import os
window = webview.create_window(
'我的应用',
url=os.path.join(os.path.dirname(__file__), 'frontend/index.html'),
width=1024,
height=768,
resizable=True
)
webview.start()
关键参数说明:
url:支持http/https远程地址、本地文件路径或HTML字符串width/height:初始窗口尺寸(单位像素)resizable:是否允许用户调整窗口大小frameless:是否隐藏标题栏(实现自定义标题栏时使用)
3. 深度交互:Python与JS双向通信
3.1 基础通信模型
pywebview通过window.pywebview.api对象实现双向通信。以下是一个完整示例:
Python端 (app.py)
python复制import webview
def greet(name):
return f'Hello, {name}!'
window = webview.create_window(
'通信示例',
'frontend/index.html',
js_api={'greet': greet} # 暴露Python函数到JS
)
webview.start()
HTML端 (frontend/index.html)
html复制<!DOCTYPE html>
<html>
<body>
<button onclick="callPython()">点击问候</button>
<script>
async function callPython() {
const response = await pywebview.api.greet('World')
alert(response)
}
</script>
</body>
</html>
3.2 高级通信模式
实际项目往往需要更复杂的交互,比如传输结构化数据或处理异步操作:
Python端增强
python复制from datetime import datetime
class API:
def __init__(self):
self.counter = 0
def get_data(self, params):
self.counter += 1
return {
'timestamp': datetime.now().isoformat(),
'params': params,
'count': self.counter
}
window = webview.create_window(
'高级通信',
'frontend/index.html',
js_api=API() # 传递整个类实例
)
JS端增强
javascript复制async function fetchData() {
try {
const result = await pywebview.api.get_data({
page: 1,
filter: 'active'
})
console.log('Received:', result)
updateUI(result)
} catch (error) {
console.error('通信失败:', error)
}
}
function updateUI(data) {
document.getElementById('output').innerHTML = `
<p>计数: ${data.count}</p>
<p>时间: ${data.timestamp}</p>
`
}
3.3 实战技巧:文件操作示例
结合Python的文件系统访问能力,实现一个简易文件浏览器:
Python端
python复制import os
from pathlib import Path
class FileManager:
@staticmethod
def list_dir(path='.'):
path = Path(path).expanduser()
return {
'path': str(path.absolute()),
'files': [
{
'name': f.name,
'is_dir': f.is_dir(),
'size': f.stat().st_size
}
for f in path.iterdir()
]
}
window = webview.create_window(
'文件管理器',
'frontend/index.html',
js_api=FileManager(),
width=800,
height=600
)
HTML端
html复制<div id="file-browser">
<h2 id="current-path"></h2>
<ul id="file-list"></ul>
</div>
<script>
let currentPath = '~'
async function loadDirectory(path) {
const data = await pywebview.api.list_dir(path)
currentPath = data.path
document.getElementById('current-path').textContent = currentPath
const fileList = document.getElementById('file-list')
fileList.innerHTML = data.files.map(file => `
<li class="${file.is_dir ? 'directory' : 'file'}">
${file.name} ${file.is_dir ? '' : `(${formatSize(file.size)})`}
</li>
`).join('')
// 添加目录点击事件
document.querySelectorAll('.directory').forEach(dir => {
dir.addEventListener('click', () => {
loadDirectory(`${currentPath}/${dir.textContent.trim()}`)
})
})
}
function formatSize(bytes) {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024*1024) return `${(bytes/1024).toFixed(1)} KB`
return `${(bytes/1024/1024).toFixed(1)} MB`
}
// 初始化加载用户目录
loadDirectory('~')
</script>
4. 高级特性与性能优化
4.1 多窗口管理与状态共享
复杂应用往往需要多个窗口协同工作。pywebview通过create_window()返回的窗口实例实现精细控制:
python复制import webview
from threading import Thread
def open_settings():
settings = webview.create_window(
'设置',
'frontend/settings.html',
width=400,
height=300,
resizable=False
)
# 窗口关闭时回调
settings.closed += lambda: print("设置窗口已关闭")
main_window = webview.create_window('主窗口', 'frontend/main.html')
# 在JS中通过按钮触发新窗口
main_window.evaluate_js("""
window.openSettings = function() {
pywebview.api.open_settings_window()
}
""")
class API:
def open_settings_window(self):
Thread(target=open_settings).start()
webview.start(api=API())
4.2 自定义窗口样式与行为
通过frameless参数和CSS配合,可以实现完全自定义的窗口标题栏:
Python端
python复制window = webview.create_window(
'自定义标题栏',
'frontend/index.html',
frameless=True,
easy_drag=False # 禁用默认拖动逻辑
)
HTML/CSS端
html复制<style>
.title-bar {
height: 30px;
background: #2c3e50;
color: white;
display: flex;
align-items: center;
padding: 0 10px;
-webkit-app-region: drag; /* 允许拖动 */
}
.window-control {
-webkit-app-region: no-drag; /* 禁止拖动 */
cursor: pointer;
}
</style>
<div class="title-bar">
<span>我的应用</span>
<div style="flex-grow: 1"></div>
<button class="window-control" onclick="window.pywebview.window.minimize()">─</button>
<button class="window-control" onclick="window.pywebview.window.toggle_fullscreen()">□</button>
<button class="window-control" onclick="window.pywebview.window.close()">✕</button>
</div>
4.3 性能优化实践
-
延迟加载策略:
python复制window = webview.create_window('优化示例', 'frontend/loading.html') window.loaded += lambda: window.load_url('frontend/main.html') -
JS/CSS资源优化:
- 使用Webpack等工具打包压缩前端资源
- 实现按需加载(如路由级别的代码分割)
-
Python端性能关键点:
- 耗时操作使用线程池(避免阻塞UI线程):
python复制from concurrent.futures import ThreadPoolExecutor pool = ThreadPoolExecutor() def heavy_task(params): return pool.submit(_real_heavy_task, params)
- 耗时操作使用线程池(避免阻塞UI线程):
-
内存管理:
- 定期调用
window.evaluate_js("gc()")触发JS垃圾回收 - Python端对大对象使用
del显式释放
- 定期调用
5. 打包与分发实战
5.1 使用PyInstaller打包
基本打包命令:
bash复制pyinstaller --onefile --windowed app.py
但需要额外处理静态资源。创建hook-webview.py:
python复制from PyInstaller.utils.hooks import collect_data_files
datas = collect_data_files('webview', subdir='lib')
然后在spec文件中添加:
python复制a = Analysis(
['app.py'],
datas=[
('frontend/*', 'frontend'),
*collect_data_files('webview')
],
...
)
5.2 跨平台打包注意事项
Windows特定配置:
- 添加manifest文件避免DPI缩放问题
- 图标处理:
python复制exe = EXE( ... icon='assets/icon.ico', name='MyApp' )
macOS特定配置:
- 创建Info.plist设置权限:
xml复制<key>NSMicrophoneUsageDescription</key> <string>需要麦克风权限进行语音输入</string> - 打包为.app:
bash复制
pyinstaller --windowed --name MyApp --osx-bundle-identifier com.example.myapp app.py
5.3 自动更新方案
实现一个简单的更新检查机制:
Python端
python复制import requests
import semver
def check_update(current_version):
try:
resp = requests.get('https://api.example.com/update/latest')
latest = resp.json()
if semver.compare(latest['version'], current_version) > 0:
return latest
except Exception:
return None
def download_update(url):
# 实现下载逻辑
pass
JS端更新流程
javascript复制async function checkForUpdates() {
const update = await pywebview.api.check_update('1.0.0')
if (update) {
const shouldUpdate = confirm(`发现新版本${update.version},是否更新?`)
if (shouldUpdate) {
showProgressBar()
await pywebview.api.download_update(update.url)
alert('更新完成,请重启应用')
window.pywebview.window.close()
}
}
}
6. 安全最佳实践
6.1 输入验证与沙箱
-
所有JS到Python的调用必须验证:
python复制def unsafe_operation(params): if not isinstance(params, dict): raise ValueError("Invalid params") # 继续处理... -
启用CSP(内容安全策略):
html复制<meta http-equiv="Content-Security-Policy" content="default-src 'self' data:; script-src 'self' 'unsafe-eval'"> -
限制本地文件访问:
python复制window = webview.create_window( '安全示例', 'frontend/index.html', local_storage=False # 禁用localStorage )
6.2 通信安全加固
-
敏感操作添加认证:
python复制class SecureAPI: def __init__(self): self._token = generate_token() def sensitive_action(self, params, token): if token != self._token: raise PermissionError("Invalid token") # 执行操作 -
使用消息加密(示例使用Fernet):
python复制from cryptography.fernet import Fernet key = Fernet.generate_key() cipher = Fernet(key) def encrypt(data: str) -> str: return cipher.encrypt(data.encode()).decode() def decrypt(encrypted: str) -> str: return cipher.decrypt(encrypted.encode()).decode()
7. 调试与问题排查
7.1 开发者工具集成
在开发模式下启用调试:
python复制window = webview.create_window(
'调试模式',
'frontend/index.html',
debug=True # 启用开发者工具
)
或者通过快捷键触发:
- Windows/Linux:
F12 - macOS:
Cmd+Option+I
7.2 常见问题解决方案
问题1:窗口白屏无内容
- 检查控制台是否有资源加载错误
- 确保文件路径正确(使用
os.path.abspath打印确认) - 尝试使用绝对路径:
python复制window.load_url(f'file://{os.path.abspath("frontend/index.html")}')
问题2:JS调用Python函数无响应
- 确认函数已通过
js_api暴露 - 检查Python控制台是否有异常
- 添加通信日志:
python复制def logged_api_call(func): def wrapper(*args, **kwargs): print(f'JS调用: {func.__name__}', args, kwargs) try: result = func(*args, **kwargs) print('返回:', result) return result except Exception as e: print('错误:', str(e)) raise return wrapper
问题3:Linux下字体渲染异常
解决方案:
css复制body {
-webkit-font-smoothing: antialiased;
font-family: 'Noto Sans', sans-serif;
}
并确保系统安装了Noto字体:
bash复制sudo apt install fonts-noto
8. 扩展应用场景
8.1 与流行框架集成
集成Vue.js的完整示例
-
使用Vue CLI创建项目:
bash复制
vue create frontend -
修改
vue.config.js:javascript复制module.exports = { publicPath: './', outputDir: '../dist-frontend', chainWebpack: config => { config.plugin('define').tap(args => { args[0]['__VUE_PROD_DEVTOOLS__'] = true return args }) } } -
Python端加载:
python复制window = webview.create_window( 'Vue应用', url='dist-frontend/index.html' )
8.2 系统托盘集成
使用pystray实现托盘图标:
python复制import pystray
from PIL import Image
import threading
def create_tray(window):
image = Image.open('icon.png')
menu = pystray.Menu(
pystray.MenuItem('显示', window.show),
pystray.MenuItem('隐藏', window.hide),
pystray.MenuItem('退出', lambda: (window.destroy(), icon.stop()))
)
icon = pystray.Icon('my_app', image, '我的应用', menu)
icon.run()
window = webview.create_window(...)
threading.Thread(target=create_tray, args=(window,), daemon=True).start()
webview.start()
8.3 硬件设备交互示例
通过pyserial与串口设备通信:
python复制import serial
class DeviceController:
def __init__(self):
self.ser = None
def connect(self, port):
if self.ser:
self.ser.close()
self.ser = serial.Serial(port, 9600, timeout=1)
return True
def send_command(self, cmd):
if not self.ser:
raise Exception('未连接设备')
self.ser.write(cmd.encode())
return self.ser.readline().decode()
window = webview.create_window(
'设备控制台',
'frontend/device.html',
js_api=DeviceController()
)
9. 性能监控与调优
9.1 内存使用分析
添加资源监控面板:
python复制import psutil
import threading
def monitor(window):
while True:
mem = psutil.virtual_memory()
window.evaluate_js(f"""
updateMetrics({{
memory: {mem.percent},
cpu: {psutil.cpu_percent()}
}})
""")
time.sleep(2)
window = webview.create_window(...)
threading.Thread(target=monitor, args=(window,), daemon=True).start()
9.2 通信性能优化
对于高频通信,使用批处理模式:
python复制class HighPerformanceAPI:
def __init__(self):
self.batch = []
self.timer = None
def add_data(self, data):
self.batch.append(data)
if not self.timer:
self.timer = threading.Timer(0.1, self.flush)
self.timer.start()
def flush(self):
if self.batch:
process_batch(self.batch)
self.batch = []
self.timer = None
10. 项目架构建议
10.1 大型项目结构
推荐的分层架构:
code复制project/
├── app/ # Python主逻辑
│ ├── core/ # 核心业务
│ ├── services/ # 后台服务
│ └── main.py # 入口文件
├── frontend/ # 前端资源
│ ├── public/ # 静态文件
│ ├── src/ # 源码
│ └── package.json # 前端依赖
├── build/ # 构建产物
└── requirements.txt # Python依赖
10.2 团队协作规范
-
接口契约:
- 定义
api_spec.md明确Python与JS的交互接口 - 使用TypeScript定义前端类型:
typescript复制declare namespace pywebview.api { function get_data(params: {page: number}): Promise<{items: string[]}> }
- 定义
-
开发流程:
mermaid复制graph LR A[前端开发] -->|Mock API| B[独立调试] C[Python开发] -->|测试脚本| D[功能验证] B & D --> E[集成测试] -
文档自动化:
使用pydoc-markdown生成API文档:bash复制
pip install pydoc-markdown pydoc-markdown -p app.core > docs/api.md
11. 测试策略
11.1 Python端测试
使用pytest测试API类:
python复制from app.api import DataAPI
import pytest
@pytest.fixture
def api():
return DataAPI()
def test_data_fetch(api):
result = api.get_data({'page': 1})
assert 'items' in result
assert isinstance(result['items'], list)
11.2 前端测试
使用Jest测试前端组件:
javascript复制// 模拟pywebview环境
global.pywebview = {
api: {
get_data: jest.fn().mockResolvedValue({
items: ['test']
})
}
}
test('加载数据', async () => {
await loadData()
expect(pywebview.api.get_data).toHaveBeenCalled()
})
11.3 端到端测试
使用Pyppeteer进行全流程测试:
python复制import asyncio
from pyppeteer import launch
async def test_ui():
browser = await launch(headless=False)
page = await browser.newPage()
await page.goto('http://localhost:8080')
await page.click('#load-btn')
await page.waitForSelector('.data-item')
items = await page.querySelectorAll('.data-item')
assert len(items) > 0
await browser.close()
asyncio.get_event_loop().run_until_complete(test_ui())
12. 持续集成部署
12.1 GitHub Actions配置
示例工作流文件.github/workflows/build.yml:
yaml复制name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
pytest tests/
12.2 自动打包发布
添加打包步骤:
yaml复制- name: Build Windows EXE
if: startsWith(github.ref, 'refs/tags/')
run: |
pip install pyinstaller
pyinstaller --onefile --windowed app/main.py
mkdir dist
cp dist/main.exe dist/MyApp-${{ github.ref_name }}.exe
- name: Upload Artifacts
uses: actions/upload-artifact@v2
with:
name: release-builds
path: dist/
13. 迁移与升级指南
13.1 从Electron迁移
关键差异对比:
| 特性 | pywebview | Electron |
|---|---|---|
| 体积 | 10MB左右 | 100MB+ |
| 启动速度 | 快(使用系统组件) | 慢(加载完整Chromium) |
| 内存占用 | 低 | 高 |
| 系统集成 | 直接调用Python | 需要Node.js扩展 |
| 跨平台一致性 | 依赖系统Web组件 | 统一Chromium行为 |
迁移步骤:
- 将
main.js中的逻辑转换为Python类 - 替换IPC通信为
pywebview.api调用 - 修改打包配置使用PyInstaller
- 测试各平台表现差异
13.2 版本升级注意
从pywebview 3.x升级到4.x的主要变化:
- WebView2成为Windows默认引擎(原为IE/EdgeHTML)
- 全局
api改为窗口实例属性 - 事件监听接口变更:
python复制# 旧版 window.events.closed += handler # 新版 window.closed += handler
建议升级路径:
- 首先确保测试覆盖率
- 逐项替换废弃接口
- 特别测试Windows下的WebView2行为
14. 社区资源与进阶学习
14.1 优质开源项目参考
-
Markdown编辑器:pywebview-markdown-editor
- 特色:集成CodeMirror实现语法高亮
- 学习点:文件操作与编辑器集成
-
音乐播放器:pywebview-music-player
- 特色:系统托盘集成与音频API使用
- 学习点:硬件交互与后台播放
-
数据库工具:sqlite-webview
- 特色:SQLite可视化操作
- 学习点:复杂数据交互模式
14.2 调试技巧宝典
场景1:JS异常捕获
javascript复制// 全局错误处理
window.addEventListener('error', (event) => {
pywebview.api.log_error({
message: event.message,
source: event.filename,
lineno: event.lineno
})
})
// Promise异常捕获
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason)
})
场景2:Python日志集成
python复制import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
handler = RotatingFileHandler('app.log', maxBytes=1e6, backupCount=3)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
# 重定向标准输出
sys.stdout = StreamToLogger(logging.INFO)
sys.stderr = StreamToLogger(logging.ERROR)
class StreamToLogger:
def __init__(self, level):
self.logger = logging.getLogger('stdout')
self.level = level
def write(self, buf):
for line in buf.rstrip().splitlines():
self.logger.log(self.level, line.rstrip())
def flush(self):
pass
15. 未来展望与替代方案
15.1 WebView2的深度集成
随着WebView2的普及,pywebview在Windows平台的能力将大幅增强:
- 支持更现代的CSS/JavaScript特性
- 更好的调试工具集成
- 性能进一步提升
可以通过以下方式检测WebView2可用性:
python复制import webview
if webview.engine == 'edgechromium':
print('使用WebView2引擎')
# 启用高级特性
else:
print('使用旧版引擎')
# 降级处理
15.2 替代技术对比
| 方案 | 适用场景 | 学习曲线 | 性能表现 |
|---|---|---|---|
| pywebview | 轻量级混合应用 | 低 | 高 |
| Electron | 复杂桌面应用 | 中 | 中 |
| PyQt WebEngine | 需要深度Qt集成 | 高 | 高 |
| Tkinter | 纯Python简单GUI | 低 | 低 |
| Flutter Desktop | 跨平台统一UI | 中 | 高 |
选择建议:
- 优先考虑pywebview:当需要快速开发且重视资源占用时
- 选择Electron:当需要绝对一致的跨平台表现时
- 考虑PyQt:当应用需要集成其他Qt组件时
16. 真实案例:企业级应用开发
16.1 项目背景
某制造业MES系统的客户端改造需求:
- 原有WinForms界面难以维护
- 需要添加实时数据可视化
- 必须支持离线操作
- 与多种PLC设备通信
16.2 技术选型
最终架构:
code复制[PLC设备] <-OPC UA-> [Python服务层] <-IPC-> [pywebview UI层]
↑
[SQLite本地数据库] ←──┘
关键组件:
- 通信层:
opcua库处理设备连接 - 数据层:SQLite本地存储
- UI层:Vue.js + ECharts可视化
16.3 性能数据
| 指标 | 原系统 (WinForms) | 新系统 (pywebview) |
|---|---|---|
| 启动时间 | 3.2秒 | 1.8秒 |
| 内存占用 | 210MB | 140MB |
| 数据刷新延迟 | 500-800ms | 200-300ms |
| 安装包大小 | 85MB | 22MB |
16.4 经验总结
- 设备通信层:将硬件操作封装为独立Python服务,通过
multiprocessing隔离 - 数据同步:实现差分更新机制,减少JS-Python通信量
- 离线缓存:使用IndexedDB存储最近数据
- 异常处理:建立三级错误恢复机制(设备级、通信级、UI级)
17. 专家级优化技巧
17.1 内存管理黑科技
Python对象回收策略
python复制import weakref
class DataCache:
def __init__(self):
self._data = {}
self._refs = weakref.WeakValueDictionary()
def store(self, key, data):
self._data[key] = data
# 弱引用允许自动回收
self._refs[key] = data
def get(self, key):
return self._refs.get(key) or self._data.get(key)
JS内存优化
javascript复制// 使用对象池避免频繁创建/销毁
const widgetPool = {
_pool: [],
get() {
return this._pool.pop() || document.createElement('div')
},
release(element) {
element.innerHTML = ''
this._pool.push(element)
}
}
17.2 渲染性能优化
CSS硬件加速
css复制.animation-element {
transform: translateZ(0);
will-change: transform, opacity;
}
高效Canvas绘制
javascript复制function drawOptimized(chart) {
// 使用离屏Canvas预渲染
const offscreen = new OffscreenCanvas(800, 600)
const ctx = offscreen.getContext('2d')
// 复杂绘制操作
renderChart(ctx)
// 主线程只进行最终绘制
requestAnimationFrame(() => {
mainCtx.drawImage(offscreen, 0, 0)
})
}
18. 无障碍访问支持
18.1 基础ARIA实践
html复制<button
id="save-btn"
aria-label="保存文档"
aria-keyshortcuts="Ctrl+S"
tabindex="0">
<svg aria-hidden="true"><!-- 图标 --></svg>
</button>
18.2 屏幕阅读器适配
Python端可提供辅助功能开关:
python复制class Accessibility:
def __init__(self):
self.high_contrast = False
def toggle_contrast(self):
self.high_contrast = not self.high_contrast
return {
'status': self.high_contrast,
'css': ':root { --text-color: #FFF; --bg-color: #000; }'
}
JS端响应变化:
javascript复制async function handleContrastChange() {
const {css} = await pywebview.api.toggle_contrast()
const style = document.getElementById('a11y-style') ||
document.head.appendChild(document.createElement('style'))
style.id = 'a11y-style'
style.textContent = css
}
19. 国际化与本地化
19.1 多语言实现方案
Python端语言包
python复制import gettext
import os
locales = {
'en': gettext.translation('app', 'locales', languages=['en']),
'zh': gettext.translation('app', 'locales', languages=['zh'])
}
def set_language(lang):
locales.get(lang, locales['en']).install()
return {'status': 'ok'}
前端同步切换
javascript复制async function changeLanguage(lang) {
await pywebview.api.set_language(lang)
window.location.reload() // 简单实现
}
// 更优方案:前端维护字典
const i18n = {
en: { welcome: 'Welcome' },
zh: { welcome: '欢迎' }
}
function updateTexts(lang) {
document.querySelector('[data-i18n]').forEach(el => {
const key = el.dataset.i18n
el.textContent = i18n[lang][key]
})
}
20. 终极实战:从零构建完整应用
20.1 项目初始化
-
创建虚拟环境:
bash复制python -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows -
安装依赖:
bash复制
pip install pywebview pyinstaller -
前端脚手架:
bash复制npm create vite@latest frontend --template vue cd frontend && npm install
20.2 核心代码结构
Python入口 (main.py)
python复制from api import AppAPI
import webview
import sys
def main():
api = AppAPI()
window = webview.create_window(
'全栈应用',
'frontend/dist/index.html',
js_api=api,
width=1200,
height=800
)
api.set_window(window)
webview.start()
if __name__ == '__main__':
main()
前端适配 (src/main.js)
javascript复制if (window.pywebview) {
// 生产环境使用真实API
window.appAPI = window.pywebview.api
} else {
// 开发环境使用Mock
window.appAPI = mockAPI
}
20.3 开发工作流
-
启动前端开发服务器:
bash复制cd frontend && npm run dev -
Python连接开发服务器:
python复制window = webview.create_window( '开发模式', 'http://localhost:5173', debug=True ) -
打包发布:
bash复制# 构建前端 cd frontend && npm run
