
python GUI库 EEL + VUE.js 开发环境配置 联调
eel开发环境启动的服务器默认端口是8000,如果前端界面的开发也是直接在EEL开发环境中进行,一切好办。根据作者官方github上的资料,eel.start()的第一个参数是启动页的html文件名(入口页面),是字符串。2、vue页面中引入eel.js的时候,引用路径为eel环境的eel.js , 以及把websocket的host设为eel环境的host。1、python代码中的eel.sta
eel开发环境启动的服务器默认端口是8000,如果前端界面的开发也是直接在EEL开发环境中进行,一切好办。但如果前端用vue,则需要另外启动专用的vue开发环境的服务器(Vue CLI (npm run serve
)默认端口是8080,Vite (npm run dev
)默认端口是5173)。
那怎么同步联调开发呢?
核心操作有两点:
1、python代码中的eel.start() 参数配置指定启动页为vue环境的入口页
2、vue页面中引入eel.js的时候,引用路径为eel环境的eel.js , 以及把websocket的host设为eel环境的host。
# main.py
import eel
@eel.expose
def say_hello_py(x):
print("Hello from %s" % x)
"""
开发环境:
1、python开发环境的eel.start()参数:设置启动页面为vue开发环境的服务端口5173,
2、vue开发环境中的public/index.html里引用eel.js时,路径是引用python eel环境的eel.js
3、vue开发环境中的public/index.html里设置websocket的服务器为python eel所启动的那个服务器。
生产环境:
和正常的一样使用
"""
def start_eel(environment):
"""判断当前是开发环境还是生产环境,选择不同的eel.start()参数配置"""
if environment == 'develop': # 开发环境
directory = 'src' # 注意!这个值对应的是EEL服务器的文件夹,不是VUE服务器的文件夹
app ='chrome'
start_page = {'port': 5173} # 指向:http://localhost:5173/
eel_kwargs = dict( # 设置 http://localhost:9000 为eel服务器
mode=app,
host="localhost",
port=9000,
)
else: # 生产环境
directory = 'web'
app = 'chrome'
start_page = 'index.html'
eel_kwargs = dict(
mode=app,
port=0,
size=(1280, 800),
)
eel.init(directory)
eel.start(start_page, **eel_kwargs)
if __name__ == "__main__":
print("启动python...")
start_eel('develop')
// vue 的 public/index.html
<%if(process.env.NODE_ENV === 'production'){ %>
<script type="text/javascript" src="/eel.js"></script>
<%}else{%>
<script type=text/javascript src="http://localhost:9000/eel.js"></script>
<script>
window.eel.set_host("ws://localhost:9000");
</script>
<%}%>
<!-- vue 中 public/index.html-->
<!DOCTYPE html>
<html>
<head>
<title>Hello, World!</title>
<script type=text/javascript src="http://localhost:9000/eel.js"></script>
<script>
window.eel.set_host("ws://localhost:9000");
</script>
<script type="text/javascript">
eel.expose(say_hello_js); // Expose this function to Python
function say_hello_js(x) {
const msg = "Hello from " + x
document.getElementById("msgbox").innerHTML=msg;
}
eel.say_hello_py("Javascript World!"); // Call a Python function
</script>
</head>
<body>
Hello, World!
<button onclick="eel.say_hello_py('Javascript Button!')">调用Python函数</button>
<p id="msgbox"></p>
<button onclick="say_hello_js('Javascript Button!')">调用JS函数</button>
</body>
</html>
==========
踩坑小记:
eel.init(directory)
当使用5173端口作前端服务时, eel.init(directory) 的directory 这个配置项对应的文件夹应该是VUE开发环境的本地文件夹。如果VUE开发环境不在本机上,你可以在本地构建一个文件夹,把需要用到的js函数的函数名放入这个文件夹中即可。
我一开始没有留意,结果是界面可以成功启动,界面启动过程没有报错,网页端调用python函数也成功,但python端调用js函数就报错提示:[AttributeError: module 'eel' has no attribute 'say_hello_js'] ,把eel.init(directory)的directory配置为vue服务的本地目录就成功了。
甚至你可以专门建一个目录,这个目录只存放一个文本文件,把所有暴露的js函数名以eel.expose(js_function_name) 的形式记录到一个文件中,并以.js为扩展名命名,也可以。
//expose_js_function_name.js
eel.expose(say_hello_js);
eel.expose(my_js_function_1);
eel.expose(my_js_function_2);
eel.expose(my_js_function_3);
eel.expose(my_js_function_4);
跟踪了一下源代码,发现确实是通过遍历该文件夹及其子目录的全部指定扩展名的文件,并通过语法解析器 EXPOSED_JS_FUNCTIONS (基于PyParsing构建)进行匹配。
EXPOSED_JS_FUNCTIONS的解释规则是:用正则表达式匹配,解析得到函数名,这些函数名被存储在js_functions这个集合中。
得到这些js函数名后,通过_mock_js_function() 构建同名函数,构建的这个函数对于eel这个类来说是全局函数,所以对于main.py来说,就是【eel.同名函数】,就可以通过eel.js_function_name() 调用了。
# 如果程序未被PyInstaller打包成exe,则返回path的绝对路径,否则exe创建的临时资源目录_MEIPASS
def _get_real_path(path: str) -> str:
if getattr(sys, 'frozen', False):
return os.path.join(sys._MEIPASS, path) # type: ignore # sys._MEIPASS is dynamically added by PyInstaller
else:
return os.path.abspath(path)
'''
当你使用 PyInstaller 将脚本+资源打包成一个exe后。运行exe时,会动态创建一个临时目录(通常是在系统的临时文件夹中),并将可执行文件内部的所有资源解压到这个临时目录。sys._MEIPASS 就是这个临时目录的路径。
'''
def init(path: str, allowed_extensions: List[str] = ['.js', '.html', '.txt', '.htm',
'.xhtml', '.vue'], js_result_timeout: int = 10000) -> None:
global root_path, _js_functions, _js_result_timeout
root_path = _get_real_path(path)
js_functions = set()
for root, _, files in os.walk(root_path): # 遍历它的子目录
for name in files:
if not any(name.endswith(ext) for ext in allowed_extensions):
continue
try:
with open(os.path.join(root, name), encoding='utf-8') as file:
contents = file.read()
expose_calls = set()
matches = EXPOSED_JS_FUNCTIONS.parseString(contents).asList() # 对文件进行解释,把【暴露给python的js函数】匹配出来。
for expose_call in matches:
# Verify that function name is valid
msg = "eel.expose() call contains '(' or '='"
assert rgx.findall(r'[\(=]', expose_call) == [], msg
expose_calls.add(expose_call) # 收集此文件的暴露函数
js_functions.update(expose_calls) # 收集全部文件的暴露函数
except UnicodeDecodeError:
pass # Malformed file probably
_js_functions = list(js_functions)
for js_function in _js_functions:
_mock_js_function(js_function) # 将找到的JS函数名称保存起来,并准备在 websocket 连接时使用
_js_result_timeout = js_result_timeout
===============================================
对于eel.start() 参数配置中的start_page参数。
根据作者官方github上的资料,eel.start()的第一个参数是启动页的html文件名(入口页面),是字符串。为什么可以接收一个dict变量{'port':5173}呢?
追踪了一下源代码,发现其值为dict类型时,可以支持的参数包含了协议scheme 、域host 、端口port 、路径path 这几个参数
其值为字符串时,字符串应该为base_url 之后的访问路径。
代码追踪:def start(*start_urls: str, **kwargs: Any) --> show(*start_urls) -->brw.open(list(start_urls), _start_args) --> open(start_pages: Iterable[Union[str, Dict[str, str]]], options: OptionsDictT) --> _build_urls(start_pages: Iterable[Union[str, Dict[str, str]]], options: OptionsDictT) -->_build_url_from_dict(page, options)
def _build_url_from_dict(page: Dict[str, str], options: OptionsDictT) -> str:
scheme = page.get('scheme', 'http')
host = page.get('host', 'localhost')
port = page.get('port', options["port"])
path = page.get('path', '')
if not isinstance(port, (int, str)):
raise TypeError("'port' option must be an integer")
return '%s://%s:%d/%s' % (scheme, host, int(port), path)
def _build_url_from_string(page: str, options: OptionsDictT) -> str:
if not isinstance(options['port'], (int, str)):
raise TypeError("'port' option must be an integer")
base_url = 'http://%s:%d/' % (options['host'], int(options['port']))
return base_url + page
更多推荐
所有评论(0)