ascript 1.3.27__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ascript/__init__.py +8 -0
- ascript/hooks/__init__.py +0 -0
- ascript/hooks/hook-ascript.py +2 -0
- ascript/windows/__init__.py +29 -0
- ascript/windows/daos/__init__.py +0 -0
- ascript/windows/daos/screen.py +143 -0
- ascript/windows/hardware/__init__.py +5 -0
- ascript/windows/hardware/keyboard.py +279 -0
- ascript/windows/hardware/logitech_driver.py +179 -0
- ascript/windows/hardware/mouse.py +308 -0
- ascript/windows/license/__init__.py +16 -0
- ascript/windows/license/achttp.cp310-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp311-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp312-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp313-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp314-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp38-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp39-win_amd64.pyd +0 -0
- ascript/windows/media/__init__.py +0 -0
- ascript/windows/resources/__init__.py +0 -0
- ascript/windows/resources/libs/logitech/x64/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/libs/logitech/x86/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/templates/__init__.py +0 -0
- ascript/windows/resources/templates/__init__py.win.template +51 -0
- ascript/windows/resources/templates/build.as.win.template +14 -0
- ascript/windows/resources/templates/build.py +71 -0
- ascript/windows/resources/templates/logo_windows.ico +0 -0
- ascript/windows/resources/templates/windows/main.py +50 -0
- ascript/windows/resources/templates/windows/pyproject.toml +44 -0
- ascript/windows/resources/templates/windows/res/img/logo.ico +0 -0
- ascript/windows/screen/__init__.py +13 -0
- ascript/windows/screen/as_cv.py +382 -0
- ascript/windows/screen/canny.py +128 -0
- ascript/windows/screen/code_scanner.py +72 -0
- ascript/windows/screen/color_algorithms.py +277 -0
- ascript/windows/screen/find_colors.py +251 -0
- ascript/windows/screen/find_images.py +217 -0
- ascript/windows/screen/ocr/__init__.py +4 -0
- ascript/windows/screen/ocr/ocr.py +223 -0
- ascript/windows/screen/ocr/rapidocr_hook.py +301 -0
- ascript/windows/screen/ocrx.py +169 -0
- ascript/windows/screen/screen_core.py +139 -0
- ascript/windows/store/__init__.py +21 -0
- ascript/windows/system/__init__.py +8 -0
- ascript/windows/system/device.py +355 -0
- ascript/windows/system/easy_print.py +115 -0
- ascript/windows/system/env.py +492 -0
- ascript/windows/system/exception/__init__.py +1 -0
- ascript/windows/system/key_value.py +145 -0
- ascript/windows/system/pip.py +175 -0
- ascript/windows/system/r.py +165 -0
- ascript/windows/system/runtime.py +62 -0
- ascript/windows/tools/__init__.py +27 -0
- ascript/windows/tools/__main__.py +21 -0
- ascript/windows/tools/action.json +33 -0
- ascript/windows/tools/action.py +168 -0
- ascript/windows/tools/colors_tool.py +391 -0
- ascript/windows/tools/config.py +26 -0
- ascript/windows/tools/dumptool.py +18 -0
- ascript/windows/tools/eel_api.py +495 -0
- ascript/windows/tools/gpplug/__init__.py +0 -0
- ascript/windows/tools/gpplug/plug.py +70 -0
- ascript/windows/tools/mouse_catch.py +168 -0
- ascript/windows/tools/plug_manager.py +38 -0
- ascript/windows/tools/tool_api.py +906 -0
- ascript/windows/tools/tool_window.py +56 -0
- ascript/windows/tools/uia_worker.py +365 -0
- ascript/windows/tools/web/app_store_window.html +481 -0
- ascript/windows/tools/web/colors.html +532 -0
- ascript/windows/tools/web/css/screen.css +1342 -0
- ascript/windows/tools/web/css/viewtree.css +357 -0
- ascript/windows/tools/web/flot_control.html +195 -0
- ascript/windows/tools/web/img/ico/favicon.png +0 -0
- ascript/windows/tools/web/img/ico/paintpalette.png +0 -0
- ascript/windows/tools/web/img/svg/find_colors.svg +1 -0
- ascript/windows/tools/web/img/svg/find_images.svg +1 -0
- ascript/windows/tools/web/img/svg/illustration-6.svg +89 -0
- ascript/windows/tools/web/img/svg/img_default.svg +1 -0
- ascript/windows/tools/web/img/svg/ocr.svg +1 -0
- ascript/windows/tools/web/js/screen/colors.js +466 -0
- ascript/windows/tools/web/js/screen/gp_plug.js +108 -0
- ascript/windows/tools/web/js/screen/image_mouse.js +464 -0
- ascript/windows/tools/web/js/screen/img_view.js +558 -0
- ascript/windows/tools/web/js/screen/plug.js +246 -0
- ascript/windows/tools/web/js/screen/screen.js +690 -0
- ascript/windows/tools/web/js/screen/screen_api.js +778 -0
- ascript/windows/tools/web/js/screen/screen_maker.js +10 -0
- ascript/windows/tools/web/js/utils/map.js +73 -0
- ascript/windows/tools/web/js/utils/upload.js +157 -0
- ascript/windows/tools/web/js/viewtree/niv_js.js +27 -0
- ascript/windows/tools/web/js/viewtree/vtree.js +1510 -0
- ascript/windows/tools/web/libs/bootstrap/css/bootstrap.min.css +6 -0
- ascript/windows/tools/web/libs/bootstrap/js/bootstrap.bundle.min.js +7 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-circle.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-square.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.css +2078 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.json +2052 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.min.css +5 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.scss +2090 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
- ascript/windows/tools/web/libs/feather-icons/dist/feather.min.js +13 -0
- ascript/windows/tools/web/libs/feather-icons/dist/icons.json +1 -0
- ascript/windows/tools/web/libs/jquery/core.js +399 -0
- ascript/windows/tools/web/libs/jquery/jquery.js +10598 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js +4 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js.zip +0 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.js +8495 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.min.js +2 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.js +2134 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.min.css +16 -0
- ascript/windows/tools/web/libs/jquery-zTree/css/zTreeStyle.css +97 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_close.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_open.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/2.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/3.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/4.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/5.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/6.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/7.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/8.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/9.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/line_conn.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/loading.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/tree-node-btn.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/js/jquery.ztree.all.min.js +3 -0
- ascript/windows/tools/web/libs/treeview/css/jquery.treeview.css +79 -0
- ascript/windows/tools/web/libs/treeview/img/file.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/ico_loading.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon4.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon5.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon6.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/py.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red.gif +0 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.edit.js +37 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.js +250 -0
- ascript/windows/tools/web/license_window.html +255 -0
- ascript/windows/tools/web/plugs/a.html +653 -0
- ascript/windows/tools/web/plugs/demo.html +52 -0
- ascript/windows/tools/web/plugs/find_colors.html +629 -0
- ascript/windows/tools/web/plugs/find_images.html +699 -0
- ascript/windows/tools/web/plugs/rapidocr.html +954 -0
- ascript/windows/tools/web/vtree.html +496 -0
- ascript/windows/tools/webview/assets/index-HymBPl-I.css +1 -0
- ascript/windows/tools/webview/assets/index-PqA_m8tL.js +17 -0
- ascript/windows/tools/webview/index.html +13 -0
- ascript/windows/ui/__init__.py +6 -0
- ascript/windows/ui/app_store_window.py +272 -0
- ascript/windows/ui/draw_window.py +231 -0
- ascript/windows/ui/flot_test.py +297 -0
- ascript/windows/ui/flot_window.py +488 -0
- ascript/windows/ui/license_window.py +111 -0
- ascript/windows/ui/web_window.py +466 -0
- ascript/windows/utils/__init__.py +0 -0
- ascript/windows/utils/node_utils.py +20 -0
- ascript/windows/utils/screen_utils.py +280 -0
- ascript/windows/window/Selector.py +1015 -0
- ascript/windows/window/Window.py +747 -0
- ascript/windows/window/__init__.py +5 -0
- ascript/windows/window/launcher.py +213 -0
- ascript/windows/window/uia_runtime.py +26 -0
- ascript-1.3.27.dist-info/METADATA +41 -0
- ascript-1.3.27.dist-info/RECORD +177 -0
- ascript-1.3.27.dist-info/WHEEL +5 -0
- ascript-1.3.27.dist-info/entry_points.txt +2 -0
- ascript-1.3.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1510 @@
|
|
|
1
|
+
// const baseUrl = 'http://' + window.location.host
|
|
2
|
+
// const baseUrl = 'http://192.168.31.59:9096'
|
|
3
|
+
let zNodes = null // 树结构信息
|
|
4
|
+
let zNodes_cache = null
|
|
5
|
+
|
|
6
|
+
let selectorSteps = [
|
|
7
|
+
{ id: 'root', method: 'Selector', isRoot: true }
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
let currentFindMode = 'find'; // 默认为 find()
|
|
11
|
+
|
|
12
|
+
var $img = $('#cellphoneScreen')[0]
|
|
13
|
+
// 在 forEachNodes 函数外部或上方定义一个变量,用于存储计时器
|
|
14
|
+
let scrollTimer = null;
|
|
15
|
+
let currentHoverId = null; // 记录当前真正悬停的 ID
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// //------------filter
|
|
19
|
+
// var node_f_visible = $("#v_f_visible").is(':checked')
|
|
20
|
+
// var node_f_text = $("#v_f_text").is(':checked')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
$(document).ready(function () { // 初始化
|
|
26
|
+
|
|
27
|
+
syncWithBrowserTheme();
|
|
28
|
+
bind_execboard_drag();
|
|
29
|
+
|
|
30
|
+
window.hwnd = getQueryString("hwnd");
|
|
31
|
+
|
|
32
|
+
refreshWindows();
|
|
33
|
+
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// 1. 刷新窗口列表的函数
|
|
38
|
+
async function refreshWindows() {
|
|
39
|
+
// --- [1] 开始动画 ---
|
|
40
|
+
const $icon = $('#refreshIcon');
|
|
41
|
+
$icon.addClass('spin-animation');
|
|
42
|
+
$('#refreshWinBtn').prop('disabled', true);
|
|
43
|
+
|
|
44
|
+
console.log("正在请求窗口列表...");
|
|
45
|
+
|
|
46
|
+
// --- [你的原始逻辑:不做任何改动] ---
|
|
47
|
+
let response = await eel.get_online_windows()();
|
|
48
|
+
|
|
49
|
+
if (response.status === "success") {
|
|
50
|
+
const $menu = $('#windowList');
|
|
51
|
+
const $selectorBtn = $('#windowSelector');
|
|
52
|
+
$menu.empty();
|
|
53
|
+
$menu.append('<li><h6 class="dropdown-header">运行中的应用</h6></li>');
|
|
54
|
+
|
|
55
|
+
response.data.forEach(win => {
|
|
56
|
+
let isMatched = (window.hwnd && String(win.hwnd) === String(window.hwnd));
|
|
57
|
+
let item = `<li><a class="dropdown-item ${isMatched ? 'active' : ''}"
|
|
58
|
+
href="javascript:void(0)"
|
|
59
|
+
onclick="selectWindow('${win.display}', ${win.hwnd}, '${win.title}')">
|
|
60
|
+
${win.display}
|
|
61
|
+
</a></li>`;
|
|
62
|
+
$menu.append(item);
|
|
63
|
+
|
|
64
|
+
if (isMatched) {
|
|
65
|
+
$selectorBtn.text(win.display);
|
|
66
|
+
$selectorBtn.attr('data-current-hwnd', win.hwnd);
|
|
67
|
+
selectWindow(win.display, win.hwnd, win.title);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- [2] 停止动画 ---
|
|
73
|
+
// 延迟 400ms 停止,防止闪烁过快
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
$icon.removeClass('spin-animation');
|
|
76
|
+
$('#refreshWinBtn').prop('disabled', false);
|
|
77
|
+
}, 400);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 自动同步浏览器(系统)的主题色给 Bootstrap
|
|
84
|
+
*/
|
|
85
|
+
function syncWithBrowserTheme() {
|
|
86
|
+
// 1. 定义修改主题的函数
|
|
87
|
+
const setTheme = theme => {
|
|
88
|
+
// 如果系统是 dark,就给 body 设置 data-bs-theme="dark"
|
|
89
|
+
document.documentElement.setAttribute('data-bs-theme', theme);
|
|
90
|
+
|
|
91
|
+
// 针对你的一体化代码框做细微调整
|
|
92
|
+
const textarea = document.getElementById('p_i_xml');
|
|
93
|
+
if (textarea) {
|
|
94
|
+
// 深色模式下用深背景,浅色模式下用浅背景
|
|
95
|
+
textarea.style.backgroundColor = (theme === 'dark') ? '#2d2d2d' : '#f8f9fa';
|
|
96
|
+
textarea.style.color = (theme === 'dark') ? '#f8f8f2' : '#212529';
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// 2. 初始检查:获取当前系统/浏览器的主题
|
|
101
|
+
const preferredTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
102
|
+
setTheme(preferredTheme);
|
|
103
|
+
|
|
104
|
+
// 3. 实时监听:如果用户在 PyCharm/系统中途切换了主题,网页自动跟着变
|
|
105
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
|
106
|
+
setTheme(e.matches ? 'dark' : 'light');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleAnalyzeClick() {
|
|
111
|
+
// 从你之前的选择器和输入框取值
|
|
112
|
+
const hwnd = $('#windowSelector').attr('data-current-hwnd');
|
|
113
|
+
const depth = $('#depthInput').val() || 0;
|
|
114
|
+
|
|
115
|
+
// 执行抽取的函数
|
|
116
|
+
startAnalyze(hwnd, depth);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
// 2. 选择窗口后的处理
|
|
121
|
+
function selectWindow(displayText, hwnd, window_title) {
|
|
122
|
+
|
|
123
|
+
// 1. 更新顶部主按钮
|
|
124
|
+
$('#windowSelector').text(displayText).attr('data-current-hwnd', hwnd);
|
|
125
|
+
$("#node_package").text(displayText);
|
|
126
|
+
window.hwnd = hwnd; // 同步全局变量
|
|
127
|
+
window.window_title = window_title;
|
|
128
|
+
|
|
129
|
+
// 2. 关键:更新列表中的选中样式
|
|
130
|
+
// 先移除所有项的 active 类
|
|
131
|
+
$('#windowList .dropdown-item').removeClass('active');
|
|
132
|
+
|
|
133
|
+
// 找到当前点击或匹配的那一项,加上 active 类
|
|
134
|
+
// 我们通过之前设置的 data-hwnd 来查找,这是最准的
|
|
135
|
+
$(`#windowList .dropdown-item[data-hwnd="${hwnd}"]`).addClass('active');
|
|
136
|
+
|
|
137
|
+
// 3. 联动执行:获取截图
|
|
138
|
+
refreshScreenshot(hwnd);
|
|
139
|
+
|
|
140
|
+
handleAnalyzeClick()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 执行探测的核心函数
|
|
145
|
+
* @param {number|string} hwnd - 目标窗口的句柄
|
|
146
|
+
*/
|
|
147
|
+
async function startAnalyze(hwnd) {
|
|
148
|
+
|
|
149
|
+
// 隐藏 搜索结果框
|
|
150
|
+
$('#nt_box').hide();
|
|
151
|
+
hideAttrView();
|
|
152
|
+
|
|
153
|
+
// 1. 确定 hwnd:如果没传参,就从按钮属性拿
|
|
154
|
+
const targetHwnd = hwnd || $('#windowSelector').attr('data-current-hwnd');
|
|
155
|
+
// 2. 内部获取 depth
|
|
156
|
+
const depth = $('#depthInput').val() || 0;
|
|
157
|
+
|
|
158
|
+
if (!targetHwnd) {
|
|
159
|
+
alert("请先选择窗口!");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 更新按钮状态(UI反馈)
|
|
164
|
+
const $btn = $('.btn-primary').first();
|
|
165
|
+
$btn.prop('disabled', true).text("正在探测...");
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// 3. 【核心衔接点】:调用 Python 获取数据
|
|
171
|
+
// 注意:eel 调用返回的是 Promise,必须加 await
|
|
172
|
+
let response = await eel.get_ui_tree_data(parseInt(targetHwnd), parseInt(depth))();
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if (response.status === "success") {
|
|
177
|
+
console.log("数据获取成功,准备填充树...");
|
|
178
|
+
|
|
179
|
+
// 4. 【关键步骤】:直接将返回的 data 传给你的 fill_tree_ui 函数
|
|
180
|
+
window.zNodes_cache = response.data;
|
|
181
|
+
fill_tree_ui(response.data);
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
} else {
|
|
185
|
+
alert("获取失败: " + response.message);
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error("通信失败:", err);
|
|
189
|
+
showToast("通信失败,请检查后端服务是否正常运行", "danger");
|
|
190
|
+
} finally {
|
|
191
|
+
$btn.prop('disabled', false).text("开始探测");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 刷新窗口截图
|
|
197
|
+
* @param {number|string} hwnd
|
|
198
|
+
*/
|
|
199
|
+
async function refreshScreenshot(hwnd) {
|
|
200
|
+
if (!hwnd) return;
|
|
201
|
+
|
|
202
|
+
console.log("正在请求窗口截图...");
|
|
203
|
+
const response = await eel.get_screenshot(hwnd)();
|
|
204
|
+
|
|
205
|
+
if (response.status === "success") {
|
|
206
|
+
// 假设你页面上有一个 id 为 targetImg 的图片标签
|
|
207
|
+
$('#cellphoneScreen').attr('src', response.data);
|
|
208
|
+
} else {
|
|
209
|
+
console.error("截图失败:", response.message);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
function submit_config() {
|
|
215
|
+
|
|
216
|
+
var attrs = []
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < dump_attr.length; i++) {
|
|
219
|
+
if ($(`#cb_${i + 1}`).is(':checked')) {
|
|
220
|
+
|
|
221
|
+
} else {
|
|
222
|
+
attrs.push(dump_attr[i])
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// alert(attrs.join(','))
|
|
227
|
+
|
|
228
|
+
let param_data = {}
|
|
229
|
+
param_data["ex_attrs"] = attrs.join(',')
|
|
230
|
+
|
|
231
|
+
var cb_other_attr = $("#cb_other").is(':checked')
|
|
232
|
+
if (cb_other_attr) {
|
|
233
|
+
cb_other_attr = 1
|
|
234
|
+
} else {
|
|
235
|
+
cb_other_attr = 2
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
param_data["other_filter"] = cb_other_attr
|
|
239
|
+
|
|
240
|
+
var timeout = $("#v_f_timeout").val()
|
|
241
|
+
|
|
242
|
+
if (timeout.length > 0) {
|
|
243
|
+
param_data["timeout"] = timeout
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// alert(JSON.stringify(param_data,0,2))
|
|
247
|
+
|
|
248
|
+
$.ajax({
|
|
249
|
+
url: `${baseUrl}/api/node/dumpconfig`,
|
|
250
|
+
type: 'post',
|
|
251
|
+
data: param_data,
|
|
252
|
+
async: true,
|
|
253
|
+
success: function (res) {
|
|
254
|
+
// alert(res)
|
|
255
|
+
|
|
256
|
+
},
|
|
257
|
+
error: function (err) {
|
|
258
|
+
// console.log('err', err)
|
|
259
|
+
alert(JSON.parse(err, 2, 0))
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getQueryString(name) {
|
|
267
|
+
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
|
268
|
+
var r = window.location.search.substr(1).match(reg);
|
|
269
|
+
if (r != null) {
|
|
270
|
+
return unescape(r[2]);
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
//function getactivity() {
|
|
277
|
+
// var strm = getQueryString("activity");
|
|
278
|
+
// if (strm == undefined || strm.length < 0 || strm == "0") {
|
|
279
|
+
// return 0;
|
|
280
|
+
// } else {
|
|
281
|
+
// return strm;
|
|
282
|
+
// }
|
|
283
|
+
//
|
|
284
|
+
//}
|
|
285
|
+
|
|
286
|
+
var zTreeObj;
|
|
287
|
+
// zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解)
|
|
288
|
+
var setting = {
|
|
289
|
+
data: {
|
|
290
|
+
key: {
|
|
291
|
+
name: "name",
|
|
292
|
+
children: "children"
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
view: {
|
|
296
|
+
addDiyDom: addDiyDom,
|
|
297
|
+
showIcon: false,
|
|
298
|
+
showLine: true, // 你之前的配置里开启了线
|
|
299
|
+
selectedMulti: false,
|
|
300
|
+
txtSelectedEnable: false,
|
|
301
|
+
dbClickExpand: false,
|
|
302
|
+
showTitle: false
|
|
303
|
+
},
|
|
304
|
+
callback: {
|
|
305
|
+
// 【核心改动 1】:点击时不让 zTree 把它设为“选中状态”
|
|
306
|
+
// 这样就不会出现那个 curSelectedNode 类名,也就没有黄色背景了
|
|
307
|
+
beforeClick: function (treeId, treeNode) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
function fill_tree_ui(zNodes) {
|
|
315
|
+
// 1. 确保数据是数组格式
|
|
316
|
+
if (!Array.isArray(zNodes)) {
|
|
317
|
+
zNodes = [zNodes];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
window.zNodes = zNodes;
|
|
321
|
+
|
|
322
|
+
const nodeCount = countNodes(zNodes);
|
|
323
|
+
$("#header_total_number").text(` 共找到控件 ${nodeCount} 个`);
|
|
324
|
+
|
|
325
|
+
// 2. 递归函数:修改显示条目为 [类型] 名字
|
|
326
|
+
function formatNodeName(node) {
|
|
327
|
+
// 如果 children 是空数组,直接删除该属性
|
|
328
|
+
if (node.children && node.children.length === 0) {
|
|
329
|
+
delete node.children;
|
|
330
|
+
node.isParent = false; // 明确标记为叶子节点
|
|
331
|
+
} else if (node.children) {
|
|
332
|
+
node.children.forEach(formatNodeName);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 执行预处理
|
|
337
|
+
zNodes.forEach(formatNodeName);
|
|
338
|
+
|
|
339
|
+
// 4. 初始化
|
|
340
|
+
$(".phone-box").remove();
|
|
341
|
+
var zTreeObj = $.fn.zTree.init($("#treeDemo"), setting, zNodes);
|
|
342
|
+
zTreeObj.expandAll(true);
|
|
343
|
+
|
|
344
|
+
setCellphoneScreenImg();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 辅助函数:统计总节点数
|
|
348
|
+
function countNodes(nodes) {
|
|
349
|
+
let count = 0;
|
|
350
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
351
|
+
count++; // 计入当前节点
|
|
352
|
+
if (nodes[i].children && nodes[i].children.length > 0) {
|
|
353
|
+
count += countNodes(nodes[i].children); // 递归计入子节点
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return count;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function screen_size() {
|
|
360
|
+
$.ajax({
|
|
361
|
+
url: `${baseUrl}/api/screen/size`,
|
|
362
|
+
type: 'get',
|
|
363
|
+
data: { device_id: getQueryString("device_id") },
|
|
364
|
+
async: false,
|
|
365
|
+
success: function (res) {
|
|
366
|
+
// alert(JSON.stringify(res,0,2))
|
|
367
|
+
// // alert(JSON.stringify(res,0,2));
|
|
368
|
+
// alert(JSON.stringify(res.data.width))
|
|
369
|
+
modelConfig.noncompatHeightPixels = res.data.height;
|
|
370
|
+
modelConfig.noncompatWidthPixels = res.data.width;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function node_package() {
|
|
376
|
+
$.ajax({
|
|
377
|
+
url: `${baseUrl}/api/node/package`,
|
|
378
|
+
type: 'get',
|
|
379
|
+
async: true,
|
|
380
|
+
success: function (res) {
|
|
381
|
+
// alert(res.data.bundle_id)
|
|
382
|
+
node_package_cache = res.data.bundle_id
|
|
383
|
+
$("#node_package").text(res.data.bundle_id)
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function setInfoData() {
|
|
389
|
+
// alert('1')
|
|
390
|
+
$('#header_pixels').text(`${modelConfig.widthPixels} * ${modelConfig.heightPixels}`)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function addDiyDom(treeId, treeNode) {
|
|
394
|
+
var $spanObj = $("#" + treeNode.tId + "_span");
|
|
395
|
+
$spanObj.empty();
|
|
396
|
+
|
|
397
|
+
// 1. 构建内容
|
|
398
|
+
let typeStr = treeNode.type || "";
|
|
399
|
+
let nameStr = treeNode.name || "";
|
|
400
|
+
let valStr = treeNode.value || "";
|
|
401
|
+
let fullHtml = `
|
|
402
|
+
<span class="node-type" style="color: var(--bs-secondary-color); margin-right: 2px;">${typeStr}</span>
|
|
403
|
+
<span class="node-name" style="color: var(--bs-body-color);">${nameStr}</span>
|
|
404
|
+
${valStr ? `<span class="node-val" style="margin-left: 10px; font-style: italic;">"${valStr}"</span>` : ""}
|
|
405
|
+
`;
|
|
406
|
+
|
|
407
|
+
// 2. 【关键】统一包装容器:无论是否 clickable,都用这个 div
|
|
408
|
+
// 这样红框覆盖寻找的 .tree-node-xxx 永远是这个 div,样式才可能统一
|
|
409
|
+
let $wrapper = $(`
|
|
410
|
+
<div class="attr-target tree-node-${treeNode.memory_id}"
|
|
411
|
+
style="display: inline-block; cursor: pointer; padding: 0px 4px; border-radius: 4px; border: 1px solid transparent; transition: all 0.1s;">
|
|
412
|
+
</div>
|
|
413
|
+
`);
|
|
414
|
+
|
|
415
|
+
// 3. 区分可点击样式(只改背景边框,不改结构)
|
|
416
|
+
if (treeNode.clickable) {
|
|
417
|
+
$wrapper.css({
|
|
418
|
+
"background-color": "var(--bs-secondary-bg)",
|
|
419
|
+
"border-color": "var(--bs-border-color)"
|
|
420
|
+
}).attr("id", "treenode_" + treeNode.nodeId);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
$wrapper.append(fullHtml);
|
|
424
|
+
$spanObj.append($wrapper);
|
|
425
|
+
|
|
426
|
+
const container = $wrapper[0];
|
|
427
|
+
|
|
428
|
+
// 4. 事件绑定:统一使用 .tree-node-highlight 类
|
|
429
|
+
container.onmouseover = function (e) {
|
|
430
|
+
// 先清理全局,避免多处高亮
|
|
431
|
+
$('.tree-node-highlight').removeClass("tree-node-highlight");
|
|
432
|
+
$(this).addClass("tree-node-highlight");
|
|
433
|
+
|
|
434
|
+
if (typeof handleMouseoverTree === "function") handleMouseoverTree(treeNode);
|
|
435
|
+
|
|
436
|
+
// $('.tree-node-locked').removeClass('tree-node-locked');
|
|
437
|
+
e.stopPropagation();
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
container.onmouseout = function (e) {
|
|
441
|
+
$(this).removeClass("tree-node-highlight");
|
|
442
|
+
if (typeof handleMouseoutTree === "function") handleMouseoutTree(treeNode);
|
|
443
|
+
e.stopPropagation();
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
container.onclick = function (e) {
|
|
447
|
+
if (typeof clickTreeNode === "function") clickTreeNode(treeNode);
|
|
448
|
+
showAttrView();
|
|
449
|
+
e.stopPropagation();
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function formatZnodes(list) {
|
|
454
|
+
function deleteEmptyArray(children) {
|
|
455
|
+
children.forEach(item => {
|
|
456
|
+
item.nodeId = nodeIdCount
|
|
457
|
+
nodeIdCount++
|
|
458
|
+
if (item.childs.length === 0) {
|
|
459
|
+
delete item.childs
|
|
460
|
+
} else {
|
|
461
|
+
formatZnodes(item.childs)
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
}
|
|
465
|
+
deleteEmptyArray(list)
|
|
466
|
+
return list
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function formatNodeAsString(dom) {
|
|
470
|
+
let attributes = [];
|
|
471
|
+
// 遍历并收集所有属性
|
|
472
|
+
for (let i = 0; i < dom.attributes.length; i++) {
|
|
473
|
+
const attr = dom.attributes[i];
|
|
474
|
+
attributes.push(`${attr.name}="${attr.value.replace(/"/g, '"')}"`); // 转义属性值中的双引号
|
|
475
|
+
}
|
|
476
|
+
// 构建包含标签和属性的字符串
|
|
477
|
+
let stringRepresentation = `<${dom.nodeName} ${attributes.join(' ')}>`;
|
|
478
|
+
// 如果需要闭合标签(对于非自闭合元素)
|
|
479
|
+
if (!dom.isSelfClosing) { // 注意:isSelfClosing不是标准属性,这里只是示意
|
|
480
|
+
stringRepresentation += `</${dom.nodeName}>`;
|
|
481
|
+
}
|
|
482
|
+
return stringRepresentation;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
function formatZnodesXml(dom, de_path) {
|
|
487
|
+
var item = {}
|
|
488
|
+
item.tag = dom.nodeName
|
|
489
|
+
item.type = dom.nodeName
|
|
490
|
+
item.nodeId = nodeIdCount
|
|
491
|
+
item.name = dom.getAttribute("name")
|
|
492
|
+
item.value = dom.getAttribute("value")
|
|
493
|
+
item.label = dom.getAttribute("label")
|
|
494
|
+
item.enabled = dom.getAttribute("enabled") === "true"
|
|
495
|
+
item.visible = dom.getAttribute("visible") === "true"
|
|
496
|
+
item.accessible = dom.getAttribute("accessible") === "true"
|
|
497
|
+
item.x = Number(dom.getAttribute("x"))
|
|
498
|
+
item.y = Number(dom.getAttribute("y"))
|
|
499
|
+
item.width = Number(dom.getAttribute("width"))
|
|
500
|
+
item.height = Number(dom.getAttribute("height"))
|
|
501
|
+
item.index = Number(dom.getAttribute("index"))
|
|
502
|
+
// item.scale = Number(dom.getAttribute("scale"))
|
|
503
|
+
item.xpath = `${de_path}/${item.type}[${item.index + 1}]`
|
|
504
|
+
item.source = formatNodeAsString(dom)
|
|
505
|
+
|
|
506
|
+
if (node_f_visible && !item.visible) {
|
|
507
|
+
return undefined
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// if(node_f_text){
|
|
511
|
+
// if(item.label==null && item.name==null && item.value==null){
|
|
512
|
+
// return undefined
|
|
513
|
+
// }
|
|
514
|
+
// }else{
|
|
515
|
+
// doms.push(dom)
|
|
516
|
+
// }
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
// alert(JSON.stringify(item,0,2))
|
|
521
|
+
|
|
522
|
+
nodeIdCount++
|
|
523
|
+
var nodes = dom.childNodes
|
|
524
|
+
if (nodes.length > 0) {
|
|
525
|
+
item.children = []
|
|
526
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
527
|
+
if (nodes[i].nodeType === 1) {
|
|
528
|
+
var cn = formatZnodesXml(nodes[i], item.xpath)
|
|
529
|
+
if (cn != undefined) {
|
|
530
|
+
item.children.push(cn)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// item.xmldom = dom
|
|
537
|
+
|
|
538
|
+
return item
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
async function setCellphoneScreenImg() {
|
|
544
|
+
// 1. 必须重新定义 $img 变量,否则 onload 内部无法识别
|
|
545
|
+
var $img = $('#cellphoneScreen')[0];
|
|
546
|
+
if (!window.hwnd || !$img) return;
|
|
547
|
+
|
|
548
|
+
// 2. 获取数据
|
|
549
|
+
const response = await eel.get_screenshot(window.hwnd)();
|
|
550
|
+
|
|
551
|
+
if (response.status === "success") {
|
|
552
|
+
const base64Data = response.data;
|
|
553
|
+
|
|
554
|
+
// 3. 【关键修改】:先绑定 onload 事件,再给 src 赋值
|
|
555
|
+
$img.onload = function () {
|
|
556
|
+
// 获取图片原始物理尺寸
|
|
557
|
+
var realW = $img.naturalWidth;
|
|
558
|
+
var realH = $img.naturalHeight;
|
|
559
|
+
|
|
560
|
+
// console.log(`检测到截图分辨率: ${realW}x${realH}`);
|
|
561
|
+
|
|
562
|
+
// 获取容器限制(确保 image_container 在 CSS 中有高度,如 height: 80vh)
|
|
563
|
+
var maxHeight = $("#image_container").height() - 25;
|
|
564
|
+
var maxWidth = ($("#image_container").width() > 1) ?
|
|
565
|
+
$("#image_container").width() - 20 : $("body").width() * 0.3;
|
|
566
|
+
|
|
567
|
+
// 防止容器高度为 0 导致计算错误
|
|
568
|
+
if (maxHeight <= 0) maxHeight = 600;
|
|
569
|
+
|
|
570
|
+
// 计算缩放比例
|
|
571
|
+
var bl = (realH > realW) ? realH / maxHeight : realW / maxWidth;
|
|
572
|
+
|
|
573
|
+
// 计算显示尺寸
|
|
574
|
+
let viewW = Math.round(realW / bl);
|
|
575
|
+
let viewH = Math.round(realH / bl);
|
|
576
|
+
|
|
577
|
+
// 更新 UI 样式
|
|
578
|
+
// 同时更新图片和父容器 phoneimgView 的宽高
|
|
579
|
+
$('#phoneimgView').css({ width: viewW + 'px', height: viewH + 'px' });
|
|
580
|
+
$('#cellphoneScreen').css({ width: viewW + 'px', height: viewH + 'px' });
|
|
581
|
+
$('#image_container').css({ width: (viewW + 20) + 'px' });
|
|
582
|
+
|
|
583
|
+
// 计算坐标转换比例
|
|
584
|
+
xDpi = realW / viewW;
|
|
585
|
+
yDpi = realH / viewH;
|
|
586
|
+
|
|
587
|
+
// 清除旧红框并重新绘制
|
|
588
|
+
$(".phone-box").remove();
|
|
589
|
+
|
|
590
|
+
// 确保 window.zNodes 已经在 fill_tree_ui 中赋值
|
|
591
|
+
if (window.zNodes) {
|
|
592
|
+
forEachNodes(window.zNodes);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 恢复排序和拖拽功能
|
|
596
|
+
if (typeof phoneBoxSort === "function") phoneBoxSort();
|
|
597
|
+
// if (typeof bind_execboard_drag === "function") bind_execboard_drag();
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// 4. 最后触发加载
|
|
601
|
+
$img.src = base64Data;
|
|
602
|
+
|
|
603
|
+
} else {
|
|
604
|
+
console.error("截图失败:", response.message);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function func(item, item1) {
|
|
609
|
+
// let awidth = ((item.rect.right - item.rect.left) / xDpi)
|
|
610
|
+
// let aheight = ((item.rect.bottom - item.rect.top) / yDpi)
|
|
611
|
+
|
|
612
|
+
// let awidth1 = ((item1.rect.right - item1.rect.left) / xDpi)
|
|
613
|
+
// let aheight1 = ((item1.rect.bottom - item1.rect.top) / yDpi)
|
|
614
|
+
return ($(item).width() * $(item).height()) - ($(item1).width() * $(item1).height());
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function phoneBoxSort() {
|
|
618
|
+
// 1. 获取所有红框并转为纯数组进行排序(数组操作比 jQuery 对象快得多)
|
|
619
|
+
var bs = $(".phone-box").get();
|
|
620
|
+
|
|
621
|
+
// 2. 排序逻辑
|
|
622
|
+
bs.sort(function (a, b) {
|
|
623
|
+
// 计算面积:长 * 宽
|
|
624
|
+
var areaA = a.offsetWidth * a.offsetHeight;
|
|
625
|
+
var areaB = b.offsetWidth * b.offsetHeight;
|
|
626
|
+
// 目标:面积大的排前面(z-index小),面积小的排后面(z-index大)
|
|
627
|
+
return areaB - areaA;
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// 3. 批量应用 z-index
|
|
631
|
+
// 注意:10000次 DOM 操作会卡顿,所以我们尽量减少操作
|
|
632
|
+
for (var i = 0; i < bs.length; i++) {
|
|
633
|
+
bs[i].style.zIndex = i + 10; // 面积最大的 z-index 为 10,之后递增
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
console.log(`已完成 ${bs.length} 个红框的层级排序`);
|
|
637
|
+
}
|
|
638
|
+
//--变大缩小
|
|
639
|
+
// 1. 事件只绑定一次,不要放在 setCellphoneScreenImg 里面
|
|
640
|
+
$(document).ready(function () {
|
|
641
|
+
bind_execboard_drag();
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
function bind_execboard_drag() {
|
|
645
|
+
var src_posi_X = 0, is_mouse_down = false, initialWidth = 0;
|
|
646
|
+
|
|
647
|
+
$(".right_bar").mousedown(function (e) {
|
|
648
|
+
is_mouse_down = true;
|
|
649
|
+
src_posi_X = e.pageX;
|
|
650
|
+
initialWidth = $("#image_container").width();
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
$(document).on("mouseup", function () {
|
|
655
|
+
if (is_mouse_down) {
|
|
656
|
+
is_mouse_down = false;
|
|
657
|
+
// 只有在松开鼠标时,才更新红框坐标系,不需要重新请求图片
|
|
658
|
+
updateImageScaleOnly();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
$(document).mousemove(function (e) {
|
|
663
|
+
if (is_mouse_down) {
|
|
664
|
+
let move_X = e.pageX - src_posi_X;
|
|
665
|
+
let newWidth = initialWidth + move_X;
|
|
666
|
+
if (newWidth > 100) {
|
|
667
|
+
$("#image_container").width(newWidth + "px");
|
|
668
|
+
// 实时调整图片高度以维持比例(可选)
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function forEachNodes(list, containerFragment = null) {
|
|
675
|
+
if (!list || list.length === 0) return;
|
|
676
|
+
|
|
677
|
+
// 如果是递归的第一层,创建一个文档片段(DocumentFragment)来提升性能
|
|
678
|
+
const isRoot = containerFragment === null;
|
|
679
|
+
const fragment = containerFragment || document.createDocumentFragment();
|
|
680
|
+
|
|
681
|
+
list.forEach(item => {
|
|
682
|
+
const uniqueId = item.memory_id;
|
|
683
|
+
|
|
684
|
+
// 1. 物理坐标转换为当前视图坐标
|
|
685
|
+
const viewX = item.rect.left / xDpi;
|
|
686
|
+
const viewY = item.rect.top / yDpi;
|
|
687
|
+
const viewW = (item.rect.right - item.rect.left) / xDpi;
|
|
688
|
+
const viewH = (item.rect.bottom - item.rect.top) / yDpi;
|
|
689
|
+
|
|
690
|
+
// 2. 核心:计算 z-index 解决覆盖问题
|
|
691
|
+
// 面积越小,层级越高。基数 1000000 足够涵盖大多数屏幕分辨率
|
|
692
|
+
const area = Math.round(viewW * viewH);
|
|
693
|
+
const zIndex = 1000000 - area;
|
|
694
|
+
|
|
695
|
+
const boxDom = document.createElement("div");
|
|
696
|
+
boxDom.className = 'phone-box';
|
|
697
|
+
boxDom.id = 'imgnode_' + uniqueId;
|
|
698
|
+
|
|
699
|
+
// 应用样式
|
|
700
|
+
Object.assign(boxDom.style, {
|
|
701
|
+
left: viewX + 'px',
|
|
702
|
+
top: viewY + 'px',
|
|
703
|
+
width: viewW + 'px',
|
|
704
|
+
height: viewH + 'px',
|
|
705
|
+
zIndex: zIndex,
|
|
706
|
+
position: 'absolute' // 确保定位生效
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// 3. 事件绑定:MouseOver
|
|
710
|
+
// 3. 事件绑定:MouseOver
|
|
711
|
+
boxDom.onmouseover = function () {
|
|
712
|
+
currentHoverId = uniqueId;
|
|
713
|
+
|
|
714
|
+
// 清空临时的 Highlight,但不清空 Locked
|
|
715
|
+
$('.tree-node-highlight').removeClass("tree-node-highlight");
|
|
716
|
+
|
|
717
|
+
const $this = $(this);
|
|
718
|
+
$this.addClass('actived-imgnode shadow-lg');
|
|
719
|
+
|
|
720
|
+
const $treeNode = $('.tree-node-' + uniqueId);
|
|
721
|
+
$treeNode.addClass("tree-node-highlight");
|
|
722
|
+
|
|
723
|
+
// 更新工具栏坐标
|
|
724
|
+
const realCenterX = Math.round(item.rect.left + (item.rect.right - item.rect.left) / 2);
|
|
725
|
+
const realCenterY = Math.round(item.rect.top + (item.rect.bottom - item.rect.top) / 2);
|
|
726
|
+
$('#header_viewcenter').text(`${realCenterX}, ${realCenterY}`);
|
|
727
|
+
|
|
728
|
+
if (scrollTimer) clearTimeout(scrollTimer);
|
|
729
|
+
|
|
730
|
+
scrollTimer = setTimeout(() => {
|
|
731
|
+
if (currentHoverId !== uniqueId) return;
|
|
732
|
+
|
|
733
|
+
if ($treeNode.length > 0) {
|
|
734
|
+
// --- 新增:施加特殊锁定样式 ---
|
|
735
|
+
clearLockedStatus(); // 触发前先清空旧的锁定
|
|
736
|
+
$treeNode.addClass('tree-node-locked');
|
|
737
|
+
|
|
738
|
+
// --- 优化:调整滚动位置为 center ---
|
|
739
|
+
$treeNode[0].scrollIntoView({
|
|
740
|
+
behavior: 'smooth',
|
|
741
|
+
block: 'center' // 改为 center,确保节点出现在列表正中
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}, 500);
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// 4. 事件绑定:MouseOut
|
|
748
|
+
boxDom.onmouseout = function () {
|
|
749
|
+
if (currentHoverId === uniqueId) currentHoverId = null;
|
|
750
|
+
if (scrollTimer) {
|
|
751
|
+
clearTimeout(scrollTimer);
|
|
752
|
+
scrollTimer = null;
|
|
753
|
+
}
|
|
754
|
+
$(this).removeClass('actived-imgnode shadow-lg');
|
|
755
|
+
$('.tree-node-' + uniqueId).removeClass("tree-node-highlight");
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// 5. 事件绑定:Click
|
|
759
|
+
boxDom.onclick = function (e) {
|
|
760
|
+
e.stopPropagation();
|
|
761
|
+
if (typeof clickTreeNode === "function") {
|
|
762
|
+
clickTreeNode(item);
|
|
763
|
+
} else if (typeof clickNode === "function") {
|
|
764
|
+
clickNode(item);
|
|
765
|
+
}
|
|
766
|
+
if (window.showAttrView) showAttrView();
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// 将生成的节点放入片段中
|
|
770
|
+
fragment.appendChild(boxDom);
|
|
771
|
+
|
|
772
|
+
// 递归处理子节点,共用同一个 fragment
|
|
773
|
+
if (item.children && item.children.length > 0) {
|
|
774
|
+
forEachNodes(item.children, fragment);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// 递归完成后,如果是最顶层,一次性将 10,000 个节点注入 DOM
|
|
779
|
+
if (isRoot) {
|
|
780
|
+
const container = document.getElementById('phoneimgView');
|
|
781
|
+
if (container) {
|
|
782
|
+
container.appendChild(fragment);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function clearLockedStatus() {
|
|
788
|
+
$('.tree-node-locked').removeClass('tree-node-locked');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
function handleNodeDialog(treeNode) {
|
|
793
|
+
// url: `${baseUrl}/cmd?a=click&rule={id=${treeNode.id}}`,
|
|
794
|
+
|
|
795
|
+
if (treeNode.id == undefined) {
|
|
796
|
+
alert("ID 为空,请尝试用代码点击,或 坐标点击");
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
$.ajax({
|
|
802
|
+
url: `${baseUrl}/cmd?a=click&rule={\"id\":\"${treeNode.id}\"}`,
|
|
803
|
+
type: 'get',
|
|
804
|
+
async: false,
|
|
805
|
+
success: function (res) {
|
|
806
|
+
console.log('res')
|
|
807
|
+
},
|
|
808
|
+
error: function (err) {
|
|
809
|
+
console.log('err', err)
|
|
810
|
+
alert('err', err)
|
|
811
|
+
}
|
|
812
|
+
})
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function handleMouseoverTree(node) {
|
|
816
|
+
// 使用 memory_id 寻找对应的红框
|
|
817
|
+
const $imgNode = $('#imgnode_' + node.memory_id);
|
|
818
|
+
|
|
819
|
+
if ($imgNode.length > 0) {
|
|
820
|
+
// 增加高亮类,保留基础类
|
|
821
|
+
$imgNode.addClass('actived-imgnode shadow-lg');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 更新中心坐标显示
|
|
825
|
+
if (node.rect) {
|
|
826
|
+
const x = Math.round(node.rect.left + (node.rect.right - node.rect.left) / 2);
|
|
827
|
+
const y = Math.round(node.rect.top + (node.rect.bottom - node.rect.top) / 2);
|
|
828
|
+
$('#header_viewcenter').text(`${x}, ${y}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function handleMouseoutTree(node) {
|
|
833
|
+
const $imgNode = $('#imgnode_' + node.memory_id);
|
|
834
|
+
if ($imgNode.length > 0) {
|
|
835
|
+
$imgNode.removeClass('actived-imgnode shadow-lg');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function postclick(id) {
|
|
840
|
+
var xhr = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject('Microsoft.XMLHTTP');
|
|
841
|
+
var url = 'http://' + window.location.host + '/cmd?a=click&rule={"id":"123"}';
|
|
842
|
+
xhr.open('GET', url, true);
|
|
843
|
+
xhr.send(null);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function scrool(t, id) {
|
|
847
|
+
var xhr = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject('Microsoft.XMLHTTP');
|
|
848
|
+
var url = 'http://' + window.location.host + '/cmd?a=click&rule={id:"' + id + '"}';
|
|
849
|
+
xhr.open('GET', url, true);
|
|
850
|
+
xhr.send(null);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function changeAttrNodeModeAndFillData(msg, id_i, id_c) {
|
|
854
|
+
const inputEl = document.getElementById(id_i);
|
|
855
|
+
const btnEl = id_c ? document.getElementById(id_c) : null;
|
|
856
|
+
|
|
857
|
+
// 核心修复:如果输入框 ID 不存在,直接跳过,不报错
|
|
858
|
+
if (!inputEl) {
|
|
859
|
+
console.warn(`未找到输入框 ID: ${id_i},跳过该属性填充。`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (msg !== null && msg !== undefined && msg !== "") {
|
|
864
|
+
// 规范化显示布尔值
|
|
865
|
+
if (msg === true) msg = "True";
|
|
866
|
+
if (msg === false) msg = "False";
|
|
867
|
+
|
|
868
|
+
inputEl.value = msg;
|
|
869
|
+
inputEl.disabled = false;
|
|
870
|
+
|
|
871
|
+
// 如果按钮存在,则启用
|
|
872
|
+
if (btnEl) {
|
|
873
|
+
btnEl.disabled = false;
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
inputEl.value = "";
|
|
877
|
+
// 如果没有值,建议不要 disabled,方便用户手动输入检索
|
|
878
|
+
// inputEl.disabled = true;
|
|
879
|
+
if (btnEl) {
|
|
880
|
+
btnEl.disabled = true; // 没数据时,点击“+”没意义,可以禁用
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function updateCodePreview() {
|
|
886
|
+
let finalCode = selectorChain.join("") + ".find_first()";
|
|
887
|
+
$("#p_i_xml").val(finalCode);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function get_attr_with_id(nid) {
|
|
891
|
+
$(".node_attr_loading").show()
|
|
892
|
+
let param_data = { device_id: getQueryString("device_id"), node_id: nid }
|
|
893
|
+
$.ajax({
|
|
894
|
+
url: `${baseUrl}/api/node/attr`,
|
|
895
|
+
type: 'get',
|
|
896
|
+
data: param_data,
|
|
897
|
+
async: true,
|
|
898
|
+
success: function (res) {
|
|
899
|
+
$(".node_attr_loading").hide()
|
|
900
|
+
res.data.id = null
|
|
901
|
+
clickTreeNode(res.data)
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
zNodes.forEach(function (data, index) {
|
|
905
|
+
if (data.id == nid) {
|
|
906
|
+
zNodes[index] = res.data
|
|
907
|
+
zNodes[index].id = nid
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
fill_tree_ui(zNodes)
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
},
|
|
916
|
+
error: function (err) {
|
|
917
|
+
// console.log('err', err)
|
|
918
|
+
alert(err)
|
|
919
|
+
$(".node_loading_attr").hide()
|
|
920
|
+
}
|
|
921
|
+
})
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function clickTreeNode(treeNode) {
|
|
925
|
+
const $container = $("#property");
|
|
926
|
+
$container.empty();
|
|
927
|
+
|
|
928
|
+
// 1. zTree 注入的内部属性黑名单(这些绝对不要显示)
|
|
929
|
+
const zTreeBlackList = [
|
|
930
|
+
'isParent', 'isFirstNode', 'isLastNode', 'isAjaxing',
|
|
931
|
+
'checked', 'checkedOld', 'nocheck', 'chkDisabled',
|
|
932
|
+
'halfCheck', 'check_Child_State', 'check_Focus',
|
|
933
|
+
'isHover', 'editNameFlag', 'tId', 'parentTId',
|
|
934
|
+
'zAsync', 'level', 'order', 'source', 'children', 'rect',"memory_id","open"
|
|
935
|
+
];
|
|
936
|
+
|
|
937
|
+
// 2. 初始化预览代码
|
|
938
|
+
currentSelector = "Selector()";
|
|
939
|
+
// updateCodeArea();
|
|
940
|
+
|
|
941
|
+
// 3. 遍历 treeNode
|
|
942
|
+
Object.keys(treeNode).forEach(key => {
|
|
943
|
+
const val = treeNode[key];
|
|
944
|
+
|
|
945
|
+
// --- 过滤逻辑 ---
|
|
946
|
+
if (typeof val === 'function') return; // 过滤掉 getParentNode 等方法
|
|
947
|
+
if (zTreeBlackList.includes(key)) return; // 过滤掉 zTree 注入的属性
|
|
948
|
+
if (key.startsWith('__')) return; // 过滤掉可能的双下划线私有变量
|
|
949
|
+
|
|
950
|
+
// 4. 调用我们规范化的一体化模板渲染
|
|
951
|
+
// 这里 key 就是 Python 传来的字段,直接作为 Selector 的 method
|
|
952
|
+
$container.append(renderPropertyRow(key, key, key, val));
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
// 5. 特殊处理:将 nodeId 显示为 id 并使用 .id() 方法
|
|
956
|
+
if (treeNode.nodeId) {
|
|
957
|
+
$container.prepend(renderPropertyRow("nodeId", "id", "id", treeNode.nodeId));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// // 6. 坐标展示(只读)
|
|
961
|
+
// if (treeNode.rect) {
|
|
962
|
+
// renderRectRow(treeNode.rect, $container);
|
|
963
|
+
// }
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
showAttrView();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function renderPropertyRow(key, label, method, value) {
|
|
970
|
+
const inputId = `p_i_${key}`;
|
|
971
|
+
const displayVal = (value === true) ? "True" : (value === false ? "False" : (value || ""));
|
|
972
|
+
|
|
973
|
+
// 核心逻辑:判断当前原始值的类型
|
|
974
|
+
let valueType = "string";
|
|
975
|
+
if (typeof value === "number") valueType = "number";
|
|
976
|
+
if (typeof value === "boolean") valueType = "boolean";
|
|
977
|
+
|
|
978
|
+
return `
|
|
979
|
+
<div class="input-group input-group-sm mb-2 shadow-sm">
|
|
980
|
+
<span class="input-group-text bg-body-secondary border-secondary-subtle text-secondary"
|
|
981
|
+
style="width: 85px; justify-content: center;">
|
|
982
|
+
${label}
|
|
983
|
+
</span>
|
|
984
|
+
<input class="form-control border-secondary-subtle bg-body"
|
|
985
|
+
id="${inputId}"
|
|
986
|
+
type="text"
|
|
987
|
+
value="${displayVal}">
|
|
988
|
+
<button class="btn btn-outline-primary border-secondary-subtle"
|
|
989
|
+
type="button"
|
|
990
|
+
style="width: 35px;"
|
|
991
|
+
data-type="${valueType}"
|
|
992
|
+
onclick="addSelectorStep('${method}', '${inputId}', this)">
|
|
993
|
+
<i class="bi bi-plus-lg"></i>
|
|
994
|
+
</button>
|
|
995
|
+
</div>`;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* 修改后的添加步骤方法
|
|
1000
|
+
*/
|
|
1001
|
+
function addSelectorStep(method, inputId, btnEl) {
|
|
1002
|
+
const inputEl = document.getElementById(inputId);
|
|
1003
|
+
const val = inputEl ? inputEl.value : "";
|
|
1004
|
+
// if (val === "" && method !== 'find_first') return;
|
|
1005
|
+
|
|
1006
|
+
// 从按钮读取预设的类型标记
|
|
1007
|
+
const paramType = btnEl ? btnEl.getAttribute('data-type') : 'string';
|
|
1008
|
+
|
|
1009
|
+
selectorSteps.push({
|
|
1010
|
+
id: Date.now(),
|
|
1011
|
+
method: method,
|
|
1012
|
+
param: val,
|
|
1013
|
+
paramType: paramType // 显式保存类型
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
renderSelectorChain();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* 渲染链条:包含动态属性和最终的 find 模式
|
|
1021
|
+
*/
|
|
1022
|
+
function renderSelectorChain() {
|
|
1023
|
+
const container = document.getElementById('p_i_xml_container');
|
|
1024
|
+
if (!container) return;
|
|
1025
|
+
container.innerHTML = '';
|
|
1026
|
+
|
|
1027
|
+
// 1. 遍历中间的属性步骤 (name, id, depth 等)
|
|
1028
|
+
selectorSteps.forEach((step) => {
|
|
1029
|
+
const span = document.createElement('span');
|
|
1030
|
+
span.className = 'code-step px-1 rounded cursor-pointer';
|
|
1031
|
+
|
|
1032
|
+
let displayText = "";
|
|
1033
|
+
if (step.isRoot) {
|
|
1034
|
+
displayText = "Selector(...)";
|
|
1035
|
+
} else {
|
|
1036
|
+
// 参数格式化逻辑
|
|
1037
|
+
let finalParam = step.paramType === 'string' ? `"${step.param}"` : step.param;
|
|
1038
|
+
displayText = `.${step.method}(${finalParam})`;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
span.innerText = displayText;
|
|
1042
|
+
|
|
1043
|
+
// 绑定编辑弹窗 (Root 不绑定)
|
|
1044
|
+
if (!step.isRoot) {
|
|
1045
|
+
span.style.color = "var(--bs-primary-text-emphasis)";
|
|
1046
|
+
span.style.fontWeight = "bold";
|
|
1047
|
+
span.onclick = (e) => showEditPopover(e, step.id);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
container.appendChild(span);
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
// 2. 追加最终的 find 动作
|
|
1054
|
+
const findSpan = document.createElement('span');
|
|
1055
|
+
findSpan.className = 'px-1 fw-bold';
|
|
1056
|
+
findSpan.style.color = "var(--bs-warning-text-emphasis)"; // 给 find() 一个不同的颜色(如黄色/橙色)
|
|
1057
|
+
findSpan.innerText = `.${currentFindMode}()`;
|
|
1058
|
+
|
|
1059
|
+
container.appendChild(findSpan);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* 弹出悬浮修改窗
|
|
1064
|
+
*/
|
|
1065
|
+
// 记录当前打开的 Popover 实例
|
|
1066
|
+
let currentPopover = null;
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* 弹出悬浮修改窗
|
|
1070
|
+
*/
|
|
1071
|
+
function showEditPopover(event, stepId) {
|
|
1072
|
+
event.stopPropagation();
|
|
1073
|
+
const target = event.currentTarget;
|
|
1074
|
+
const step = selectorSteps.find(s => s.id === stepId);
|
|
1075
|
+
|
|
1076
|
+
hideActivePopover();
|
|
1077
|
+
|
|
1078
|
+
currentPopover = new bootstrap.Popover(target, {
|
|
1079
|
+
html: true,
|
|
1080
|
+
title: `修改参数`,
|
|
1081
|
+
container: 'body',
|
|
1082
|
+
content: () => {
|
|
1083
|
+
const wrapper = document.createElement('div');
|
|
1084
|
+
wrapper.innerHTML = `
|
|
1085
|
+
<div class="d-flex flex-column gap-2">
|
|
1086
|
+
<div class="input-group input-group-sm" style="width: 240px;">
|
|
1087
|
+
<input type="text" id="pop-input-${stepId}"
|
|
1088
|
+
class="form-control bg-dark text-white border-secondary"
|
|
1089
|
+
value='${step.param}'>
|
|
1090
|
+
<button class="btn btn-primary btn-sm" onclick="saveStepEdit(${stepId})">确定</button>
|
|
1091
|
+
</div>
|
|
1092
|
+
<button class="btn btn-sm btn-outline-danger w-100" onclick="deleteSelectorStep(${stepId})">
|
|
1093
|
+
<i class="bi bi-trash"></i> 删除该属性
|
|
1094
|
+
</button>
|
|
1095
|
+
</div>`;
|
|
1096
|
+
return wrapper;
|
|
1097
|
+
},
|
|
1098
|
+
placement: 'top',
|
|
1099
|
+
trigger: 'manual'
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
currentPopover.show();
|
|
1103
|
+
target.classList.add('active-edit');
|
|
1104
|
+
|
|
1105
|
+
setTimeout(() => {
|
|
1106
|
+
const input = document.getElementById(`pop-input-${stepId}`);
|
|
1107
|
+
if (input) {
|
|
1108
|
+
input.focus();
|
|
1109
|
+
input.select();
|
|
1110
|
+
|
|
1111
|
+
if (step.paramType === 'number') {
|
|
1112
|
+
input.onkeypress = (e) => {
|
|
1113
|
+
if (!/[0-9.\-]/.test(e.key)) e.preventDefault();
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
input.onkeyup = (e) => { if (e.key === 'Enter') saveStepEdit(stepId); };
|
|
1117
|
+
}
|
|
1118
|
+
}, 100);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* 删除指定的属性步骤
|
|
1123
|
+
*/
|
|
1124
|
+
window.deleteSelectorStep = function (stepId) {
|
|
1125
|
+
// 过滤掉当前点击的这个 step
|
|
1126
|
+
selectorSteps = selectorSteps.filter(step => step.id !== stepId);
|
|
1127
|
+
|
|
1128
|
+
// 关闭弹窗
|
|
1129
|
+
hideActivePopover();
|
|
1130
|
+
|
|
1131
|
+
// 重新渲染链条
|
|
1132
|
+
renderSelectorChain();
|
|
1133
|
+
|
|
1134
|
+
console.log(`Step ${stepId} deleted.`);
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* 保存修改
|
|
1139
|
+
*/
|
|
1140
|
+
window.saveStepEdit = function (stepId) {
|
|
1141
|
+
const input = document.getElementById(`pop-input-${stepId}`);
|
|
1142
|
+
if (!input) return;
|
|
1143
|
+
|
|
1144
|
+
const step = selectorSteps.find(s => s.id === stepId);
|
|
1145
|
+
let newVal = input.value.trim();
|
|
1146
|
+
|
|
1147
|
+
// --- 核心校验逻辑 ---
|
|
1148
|
+
if (step.paramType === 'number') {
|
|
1149
|
+
// 如果不是纯数字(允许负数或小数点),则弹出提示
|
|
1150
|
+
if (newVal !== "" && isNaN(newVal)) {
|
|
1151
|
+
input.classList.add('is-invalid'); // 触发 Bootstrap 的错误样式
|
|
1152
|
+
alert("该字段仅支持输入数字!");
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
else if (step.paramType === 'boolean') {
|
|
1157
|
+
// 强制只能输入 True 或 False (Python 风格)
|
|
1158
|
+
const boolMap = { "true": "True", "false": "False", "t": "True", "f": "False" };
|
|
1159
|
+
let lowerVal = newVal.toLowerCase();
|
|
1160
|
+
if (boolMap[lowerVal]) {
|
|
1161
|
+
newVal = boolMap[lowerVal];
|
|
1162
|
+
} else {
|
|
1163
|
+
alert("该字段仅支持输入 True 或 False!");
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
step.param = newVal;
|
|
1169
|
+
hideActivePopover();
|
|
1170
|
+
renderSelectorChain();
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* 隐藏并清理
|
|
1175
|
+
*/
|
|
1176
|
+
function hideActivePopover() {
|
|
1177
|
+
if (currentPopover) {
|
|
1178
|
+
currentPopover.dispose();
|
|
1179
|
+
currentPopover = null;
|
|
1180
|
+
}
|
|
1181
|
+
// 移除所有高亮状态
|
|
1182
|
+
document.querySelectorAll('.code-step').forEach(el => el.classList.remove('active-edit'));
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* 全局点击监听:点击弹窗以外区域自动关闭
|
|
1187
|
+
*/
|
|
1188
|
+
document.addEventListener('mousedown', function (e) {
|
|
1189
|
+
const popoverEl = document.querySelector('.popover');
|
|
1190
|
+
// 如果点击的既不是弹窗本身,也不是正在编辑的代码块,就关闭
|
|
1191
|
+
if (popoverEl && !popoverEl.contains(e.target) && !e.target.classList.contains('code-step')) {
|
|
1192
|
+
hideActivePopover();
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* 设置查找模式并重新生成预览
|
|
1202
|
+
*/
|
|
1203
|
+
function setFindMode(mode) {
|
|
1204
|
+
currentFindMode = mode;
|
|
1205
|
+
|
|
1206
|
+
// 更新按钮显示的文字
|
|
1207
|
+
const btn = document.getElementById('find_mode_btn');
|
|
1208
|
+
if (btn) {
|
|
1209
|
+
btn.innerText = mode + '()';
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// 触发重新渲染预览区
|
|
1213
|
+
renderSelectorChain();
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
function getFullSelectorString() {
|
|
1218
|
+
let base = "";
|
|
1219
|
+
selectorSteps.forEach(step => {
|
|
1220
|
+
if (step.isRoot) {
|
|
1221
|
+
base += `Selector(title="${window.window_title}")`;
|
|
1222
|
+
} else {
|
|
1223
|
+
let param = step.paramType === 'string' ? `"${step.param}"` : step.param;
|
|
1224
|
+
base += `.${step.method}(${param})`;
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
return base + `.${currentFindMode}()`;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// 修改原有的 copySelector
|
|
1231
|
+
function copySelector() {
|
|
1232
|
+
const fullCode = getFullSelectorString();
|
|
1233
|
+
navigator.clipboard.writeText(fullCode).then(() => {
|
|
1234
|
+
showToast("代码已复制到剪贴板\n" + fullCode, "success");
|
|
1235
|
+
}).catch(() => {
|
|
1236
|
+
showToast("复制失败,请手动选择", "danger");
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async function testSelector() {
|
|
1241
|
+
const fullCode = getFullSelectorString();
|
|
1242
|
+
|
|
1243
|
+
// 1. 发起搜索前,如果当前不是搜索结果状态,记录下原始数据
|
|
1244
|
+
// 假设你的全量数据存储在 globalZNodes 变量中,或者从 zTree 对象获取
|
|
1245
|
+
const treeObj = $.fn.zTree.getZTreeObj("treeDemo");
|
|
1246
|
+
if (!document.getElementById('nt_box').style.display || document.getElementById('nt_box').style.display === 'none') {
|
|
1247
|
+
lastZNodes = treeObj.getNodes();
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// 调用 Python
|
|
1251
|
+
const response = await eel.test_selector(fullCode)();
|
|
1252
|
+
|
|
1253
|
+
if (response.status === "success") {
|
|
1254
|
+
// 2. 显示通知栏
|
|
1255
|
+
const ntBox = document.getElementById('nt_box');
|
|
1256
|
+
const ntMsg = document.getElementById('nt_msg');
|
|
1257
|
+
ntBox.style.display = 'flex';
|
|
1258
|
+
ntMsg.innerText = `当前为搜索结果,共找到 ${response.data.length} 个控件`;
|
|
1259
|
+
|
|
1260
|
+
// 3. 渲染搜索结果
|
|
1261
|
+
// 注意:这里需要根据你的 zTree 初始化逻辑,传入搜索到的结果数据
|
|
1262
|
+
// $.fn.zTree.init($("#treeDemo"), setting, response.data);
|
|
1263
|
+
fill_tree_ui(response.data);
|
|
1264
|
+
|
|
1265
|
+
// 绑定复制功能
|
|
1266
|
+
document.getElementById('copy_nt_code').onclick = () => {
|
|
1267
|
+
copySelector();
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// 绑定关闭/还原功能
|
|
1271
|
+
document.getElementById('close_nt').onclick = () => {
|
|
1272
|
+
restoreOriginalTree();
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
} else {
|
|
1276
|
+
alert("执行错误: " + response.message);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* 重置选择器链条:直接清除,不弹窗
|
|
1282
|
+
*/
|
|
1283
|
+
function resetSelectorChain() {
|
|
1284
|
+
// 1. 记录当前的窗口名,防止重置后还要重新输入窗口
|
|
1285
|
+
const currentWin = (selectorSteps.length > 0) ? selectorSteps[0].windowTitle : "当前窗口";
|
|
1286
|
+
|
|
1287
|
+
// 2. 彻底重置数组,只保留根节点和当前的窗口名
|
|
1288
|
+
selectorSteps = [
|
|
1289
|
+
{
|
|
1290
|
+
id: 'root',
|
|
1291
|
+
method: 'Selector',
|
|
1292
|
+
windowTitle: currentWin,
|
|
1293
|
+
isRoot: true
|
|
1294
|
+
}
|
|
1295
|
+
];
|
|
1296
|
+
|
|
1297
|
+
// 3. 必须清理掉可能存在的编辑弹窗,否则弹窗会残留
|
|
1298
|
+
hideActivePopover();
|
|
1299
|
+
|
|
1300
|
+
// 4. 重新刷新界面
|
|
1301
|
+
renderSelectorChain();
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* 撤销上一步:同样建议直接撤销,不设阻碍
|
|
1306
|
+
*/
|
|
1307
|
+
function undoSelectorStep() {
|
|
1308
|
+
if (selectorSteps.length > 1) {
|
|
1309
|
+
selectorSteps.pop();
|
|
1310
|
+
hideActivePopover();
|
|
1311
|
+
renderSelectorChain();
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* 抽取通用的气泡弹窗方法
|
|
1317
|
+
* @param {string} message 提示内容
|
|
1318
|
+
* @param {string} type 颜色类型 (success, danger, warning)
|
|
1319
|
+
*/
|
|
1320
|
+
function showToast(message, type = 'success') {
|
|
1321
|
+
// 1. 创建 Toast 容器(如果不存在)
|
|
1322
|
+
let container = document.getElementById('toast-container');
|
|
1323
|
+
if (!container) {
|
|
1324
|
+
container = document.createElement('div');
|
|
1325
|
+
container.id = 'toast-container';
|
|
1326
|
+
container.style.cssText = `
|
|
1327
|
+
position: fixed;
|
|
1328
|
+
bottom: 80px;
|
|
1329
|
+
left: 50%;
|
|
1330
|
+
transform: translateX(-50%);
|
|
1331
|
+
z-index: 9999;
|
|
1332
|
+
display: flex;
|
|
1333
|
+
flex-direction: column;
|
|
1334
|
+
align-items: center;
|
|
1335
|
+
gap: 10px;
|
|
1336
|
+
pointer-events: none;
|
|
1337
|
+
`;
|
|
1338
|
+
document.body.appendChild(container);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// 2. 创建单个 Toast 元素
|
|
1342
|
+
const toast = document.createElement('div');
|
|
1343
|
+
const bgClass = type === 'success' ? '#28a745' : (type === 'danger' ? '#dc3545' : '#ffc107');
|
|
1344
|
+
|
|
1345
|
+
toast.style.cssText = `
|
|
1346
|
+
background-color: ${bgClass};
|
|
1347
|
+
color: white;
|
|
1348
|
+
padding: 8px 16px;
|
|
1349
|
+
border-radius: 4px;
|
|
1350
|
+
font-size: 13px;
|
|
1351
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
1352
|
+
opacity: 0;
|
|
1353
|
+
transform: translateY(20px);
|
|
1354
|
+
transition: all 0.3s ease;
|
|
1355
|
+
white-space: nowrap;
|
|
1356
|
+
pointer-events: auto;
|
|
1357
|
+
`;
|
|
1358
|
+
toast.innerText = message;
|
|
1359
|
+
|
|
1360
|
+
container.appendChild(toast);
|
|
1361
|
+
|
|
1362
|
+
// 3. 触发动画:淡入并向上浮动
|
|
1363
|
+
setTimeout(() => {
|
|
1364
|
+
toast.style.opacity = '1';
|
|
1365
|
+
toast.style.transform = 'translateY(0)';
|
|
1366
|
+
}, 10);
|
|
1367
|
+
|
|
1368
|
+
// 4. 定时销毁:2.5秒后淡出,3秒后完全移除
|
|
1369
|
+
setTimeout(() => {
|
|
1370
|
+
toast.style.opacity = '0';
|
|
1371
|
+
toast.style.transform = 'translateY(-20px)';
|
|
1372
|
+
setTimeout(() => toast.remove(), 300);
|
|
1373
|
+
}, 2500);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* 1. 显示属性面板 (优化:已显示不重复动画)
|
|
1380
|
+
*/
|
|
1381
|
+
function showAttrView() {
|
|
1382
|
+
const $panel = $('#attrView');
|
|
1383
|
+
// 如果已经打开,直接返回,避免动画闪烁
|
|
1384
|
+
if ($panel.hasClass('is-open') && $panel.is(':visible')) {
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
$panel.stop(true, true).fadeIn(150, function () {
|
|
1389
|
+
// jQuery fadeIn 默认转 block,这里强行恢复 flex 确保布局对齐
|
|
1390
|
+
$(this).css('display', 'flex');
|
|
1391
|
+
}).addClass('is-open');
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* 2. 隐藏属性面板
|
|
1396
|
+
*/
|
|
1397
|
+
function hideAttrView() {
|
|
1398
|
+
const $panel = $('#attrView');
|
|
1399
|
+
if (!$panel.hasClass('is-open')) return;
|
|
1400
|
+
|
|
1401
|
+
$panel.stop(true, true).fadeOut(150).removeClass('is-open');
|
|
1402
|
+
|
|
1403
|
+
// 如果有正在编辑的 Popover 气泡,顺便关掉
|
|
1404
|
+
if (window.hideActivePopover) window.hideActivePopover();
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* 3. 全局点击监听 (处理“点击其他区域隐藏”)
|
|
1409
|
+
*/
|
|
1410
|
+
$(document).on('mousedown', function (e) {
|
|
1411
|
+
const $panel = $('#attrView');
|
|
1412
|
+
|
|
1413
|
+
// 面板未打开时,不浪费性能
|
|
1414
|
+
if (!$panel.hasClass('is-open')) return;
|
|
1415
|
+
|
|
1416
|
+
// --- 判定判定区 ---
|
|
1417
|
+
const isInsidePanel = $panel.is(e.target) || $panel.has(e.target).length > 0;
|
|
1418
|
+
const isPopover = $(e.target).closest('.popover').length > 0;
|
|
1419
|
+
const isCodeStep = $(e.target).closest('.code-step').length > 0;
|
|
1420
|
+
|
|
1421
|
+
// --- 精确豁免区:点击这些元素不会导致面板隐藏 ---
|
|
1422
|
+
// .phone-box: 左侧红框
|
|
1423
|
+
// .attr-target: 右侧 zTree 的自定义条目容器
|
|
1424
|
+
const isRedBox = $(e.target).closest('.phone-box').length > 0;
|
|
1425
|
+
const isDiyItem = $(e.target).closest('.attr-target').length > 0;
|
|
1426
|
+
|
|
1427
|
+
// 只有当以上都不是时,才隐藏面板
|
|
1428
|
+
if (!isInsidePanel && !isPopover && !isCodeStep && !isRedBox && !isDiyItem) {
|
|
1429
|
+
hideAttrView();
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* 4. 监听 Esc 按键隐藏
|
|
1435
|
+
*/
|
|
1436
|
+
$(document).on('keydown', function (e) {
|
|
1437
|
+
if (e.key === 'Escape') {
|
|
1438
|
+
hideAttrView();
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
function updateImageScaleOnly() {
|
|
1443
|
+
var $img = $('#cellphoneScreen')[0];
|
|
1444
|
+
if (!$img || !$img.src || $img.naturalWidth === 0) return;
|
|
1445
|
+
|
|
1446
|
+
var realW = $img.naturalWidth;
|
|
1447
|
+
var realH = $img.naturalHeight;
|
|
1448
|
+
|
|
1449
|
+
// 1. 获取容器宽度
|
|
1450
|
+
var containerW = $("#image_container").width();
|
|
1451
|
+
|
|
1452
|
+
// 2. 【核心修复】:动态获取窗口高度,减去工具栏等占用的高度
|
|
1453
|
+
// 不要直接信任容器的 .height(),因为它可能被 CSS 限制死了
|
|
1454
|
+
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
1455
|
+
var containerH = vh - $("#con_tools_bar").height() - 40; // 减去底部工具栏和间距
|
|
1456
|
+
|
|
1457
|
+
var paddingX = 20;
|
|
1458
|
+
var maxWidth = Math.max(containerW - paddingX, 50);
|
|
1459
|
+
var maxHeight = Math.max(containerH, 50);
|
|
1460
|
+
|
|
1461
|
+
// 3. 计算缩放比
|
|
1462
|
+
var blW = realW / maxWidth;
|
|
1463
|
+
var blH = realH / maxHeight;
|
|
1464
|
+
|
|
1465
|
+
// 如果你希望拖动宽度时图片有反应,这里可以加一个权重判断
|
|
1466
|
+
// 或者干脆采用“适应宽度”模式
|
|
1467
|
+
var bl = Math.max(blW, blH);
|
|
1468
|
+
|
|
1469
|
+
let viewW = Math.round(realW / bl);
|
|
1470
|
+
let viewH = Math.round(realH / bl);
|
|
1471
|
+
|
|
1472
|
+
$('#phoneimgView, #cellphoneScreen').css({
|
|
1473
|
+
width: viewW + 'px',
|
|
1474
|
+
height: viewH + 'px'
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
window.xDpi = realW / viewW;
|
|
1478
|
+
window.yDpi = realH / viewH;
|
|
1479
|
+
|
|
1480
|
+
$(".phone-box").remove();
|
|
1481
|
+
if (window.zNodes) forEachNodes(window.zNodes);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
|
|
1486
|
+
function restoreOriginalTree() {
|
|
1487
|
+
if (window.zNodes_cache) {
|
|
1488
|
+
fill_tree_ui(window.zNodes_cache);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
$('#nt_box').hide();
|
|
1492
|
+
// window.zNodes_cache = null;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
function hideparams() {
|
|
1499
|
+
tipClose();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function back() {
|
|
1503
|
+
window.location.href = 'http://' + window.location.host;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
function refesh() {
|
|
1507
|
+
window.location.reload();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
|