@uni_toolkit/uniapp-miniprogram-devtool 0.1.1-alpha.1778855349600

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.
@@ -0,0 +1,882 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_chunk = require("./chunk-CKQMccvm.cjs");
3
+ let node_child_process = require("node:child_process");
4
+ let node_http = require("node:http");
5
+ node_http = require_chunk.__toESM(node_http);
6
+ //#region src/web-panel.ts
7
+ const DEFAULT_SNAPSHOT = {
8
+ connected: false,
9
+ status: "正在启动...",
10
+ route: "",
11
+ rows: [],
12
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13
+ };
14
+ function html() {
15
+ return `<!doctype html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="utf-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20
+ <title>uniappx 运行时映射</title>
21
+ <style>
22
+ :root { --ink:#17211d; --muted:#68736d; --paper:#f8f1df; --card:#fffaf0; --line:#dfd3b9; --green:#1f7a54; --red:#b33d32; --blue:#205c82; --amber:#a66b00; --flash:#f6d365; }
23
+ * { box-sizing:border-box; }
24
+ body { margin:0; min-height:100vh; color:var(--ink); background:radial-gradient(circle at 10% 5%, #cbeebc 0, transparent 28rem), radial-gradient(circle at 90% 20%, #bdd7ef 0, transparent 26rem), linear-gradient(135deg, #f8f1df, #ecdec0); font:15px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
25
+ header { padding:36px clamp(18px,5vw,72px) 18px; display:flex; justify-content:space-between; gap:24px; align-items:flex-end; }
26
+ h1 { margin:0; font:800 clamp(34px,7vw,78px)/.92 Georgia, 'Times New Roman', serif; letter-spacing:-.055em; }
27
+ .meta { color:var(--muted); text-align:right; }
28
+ main { padding:0 clamp(18px,5vw,72px) 64px; display:grid; gap:18px; }
29
+ .card { background:rgba(255,250,240,.92); border:1px solid var(--line); border-radius:28px; box-shadow:0 22px 70px rgba(61,44,15,.12); overflow:hidden; }
30
+ .toolbar { display:flex; justify-content:space-between; gap:18px; padding:20px 24px; border-bottom:1px solid var(--line); align-items:center; }
31
+ .actions { display:flex; align-items:center; gap:12px; flex-wrap:wrap; justify-content:flex-end; }
32
+ .toggles { display:flex; flex-wrap:wrap; gap:10px; padding:0 24px 18px; }
33
+ .toggle-group { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
34
+ .toggle-label { color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.08em; font-weight:800; }
35
+ button { appearance:none; border:1px solid #b9a98b; background:#1f7a54; color:#fffaf0; border-radius:999px; padding:10px 16px; font:800 13px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; cursor:pointer; box-shadow:0 8px 20px rgba(31,122,84,.18); }
36
+ button.secondary { background:#fffaf0; color:#17211d; }
37
+ button.secondary.active { background:#205c82; color:#fffaf0; border-color:#205c82; box-shadow:0 8px 20px rgba(32,92,130,.18); }
38
+ button:disabled { cursor:not-allowed; opacity:.45; filter:none; box-shadow:none; }
39
+ button:hover { filter:brightness(1.06); }
40
+ button:active { transform:translateY(1px); }
41
+ input { border:1px solid #b9a98b; background:#fffaf0; border-radius:999px; padding:10px 14px; min-width:220px; font:800 13px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color:var(--ink); }
42
+ .status { display:inline-flex; align-items:center; gap:10px; font-weight:800; }
43
+ .dot { width:12px; height:12px; border-radius:999px; background:var(--red); box-shadow:0 0 0 6px rgba(179,61,50,.12); }
44
+ .connected .dot { background:var(--green); box-shadow:0 0 0 6px rgba(31,122,84,.12); }
45
+ .route { color:var(--blue); font-weight:800; }
46
+ table { width:100%; border-collapse:collapse; }
47
+ th, td { text-align:left; padding:15px 18px; border-bottom:1px solid #eadfc7; vertical-align:top; }
48
+ th { color:var(--muted); font-size:12px; letter-spacing:.08em; text-transform:uppercase; }
49
+ tr.flash { animation: flash 1.05s ease-out; }
50
+ @keyframes flash { 0% { background:rgba(246,211,101,.75); } 100% { background:transparent; } }
51
+ .key { display:inline-grid; min-width:34px; min-height:34px; place-items:center; color:var(--paper); background:var(--ink); border-radius:12px; font-weight:900; }
52
+ .value { font-size:18px; font-weight:800; word-break:break-word; }
53
+ .diff { color:var(--amber); font-size:12px; margin-top:4px; }
54
+ .unknown-group-row td { background:#f6ead2; }
55
+ .unknown-group-toggle { display:flex; width:100%; justify-content:space-between; gap:12px; align-items:center; color:var(--ink); font-weight:900; text-decoration:none; }
56
+ .unknown-group-toggle:hover { text-decoration:none; }
57
+ .unknown-group-meta { color:var(--muted); font-size:12px; font-weight:800; }
58
+ .muted { color:var(--muted); }
59
+ .empty { padding:36px 24px; color:var(--muted); }
60
+ .diagnostics { padding:0 24px 24px; }
61
+ .diagnostics-card { border-top:1px solid var(--line); padding-top:18px; }
62
+ .diagnostics-grid { display:grid; gap:12px; margin-top:12px; }
63
+ .diagnostic-item { border:1px solid #e4d5b8; border-radius:18px; background:#fff7ea; padding:14px 16px; }
64
+ .diagnostic-head { display:flex; flex-wrap:wrap; justify-content:space-between; gap:8px; align-items:center; }
65
+ .diagnostic-title { font-weight:900; }
66
+ .diagnostic-meta { color:var(--muted); font-size:12px; }
67
+ .diagnostic-expression { margin-top:8px; font-size:13px; color:var(--ink); word-break:break-word; }
68
+ .diagnostic-usages { margin-top:10px; display:flex; flex-wrap:wrap; gap:8px; }
69
+ .snippet { display:inline-block; border:1px solid #dbc7a3; border-radius:999px; padding:5px 9px; background:#f6ead2; font-size:12px; max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
70
+ .template-explorer { padding:0 24px 24px; }
71
+ .template-shell { border-top:1px solid var(--line); padding-top:18px; display:grid; grid-template-columns:minmax(280px, 420px) minmax(0, 1fr); gap:16px; }
72
+ .template-tree, .template-detail { border:1px solid #e4d5b8; border-radius:22px; background:#fff7ea; min-height:220px; }
73
+ .template-pane-title { padding:14px 16px; border-bottom:1px solid #eadfc7; font-weight:900; }
74
+ .template-pane-body { padding:14px 16px; }
75
+ .template-tools { display:flex; flex-wrap:wrap; gap:10px; margin-bottom:12px; align-items:center; }
76
+ .template-search { width:100%; margin-bottom:12px; }
77
+ .template-search.with-tools { margin-bottom:0; flex:1 1 220px; min-width:180px; }
78
+ .template-list { display:grid; gap:6px; }
79
+ .template-node { border:0; background:transparent; width:100%; text-align:left; padding:6px 10px; border-radius:12px; cursor:pointer; color:var(--ink); }
80
+ .template-node:hover { background:#f6ead2; box-shadow:none; }
81
+ .template-node.active { background:#205c82; color:#fffaf0; }
82
+ .template-node.match:not(.active) { background:#fdf2cc; }
83
+ .template-node-row { display:flex; align-items:center; gap:8px; }
84
+ .template-indent { display:inline-block; width:calc(var(--depth) * 12px); flex:0 0 calc(var(--depth) * 12px); }
85
+ .template-toggle { display:inline-grid; place-items:center; width:18px; height:18px; border-radius:999px; border:1px solid #d4c09a; background:#fffaf0; font-size:11px; color:var(--muted); }
86
+ .template-toggle.placeholder { visibility:hidden; }
87
+ .template-tag { font-weight:900; }
88
+ .template-kind { font-size:11px; color:var(--muted); }
89
+ .template-count { margin-left:auto; font-size:11px; color:inherit; opacity:.8; }
90
+ .template-highlight { box-shadow:inset 0 0 0 1px rgba(246,211,101,.9); }
91
+ .template-summary { color:var(--muted); font-size:12px; margin-top:6px; }
92
+ .template-detail-grid { display:grid; gap:12px; }
93
+ .template-detail-block { display:grid; gap:6px; }
94
+ .template-detail-label { color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.08em; }
95
+ .template-breadcrumb { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
96
+ .template-crumb { display:inline-flex; align-items:center; gap:8px; }
97
+ .template-sep { color:var(--muted); }
98
+ .template-bindings { display:grid; gap:10px; }
99
+ .binding-card { border:1px solid #eadfc7; border-radius:16px; background:#fffaf0; padding:12px 14px; }
100
+ .binding-head { display:flex; flex-wrap:wrap; gap:8px; justify-content:space-between; align-items:center; }
101
+ .binding-value { margin-top:6px; font-size:14px; word-break:break-word; }
102
+ .binding-snippets { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; }
103
+ .binding-table-wrap { overflow:auto; border:1px solid #eadfc7; border-radius:16px; background:#fffaf0; }
104
+ .binding-table { width:100%; border-collapse:collapse; }
105
+ .binding-table th, .binding-table td { padding:10px 12px; border-bottom:1px solid #eadfc7; text-align:left; vertical-align:top; }
106
+ .binding-table th { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.08em; }
107
+ .binding-table tr:last-child td { border-bottom:0; }
108
+ .mini-link { appearance:none; background:transparent; border:0; padding:0; margin:0; color:inherit; cursor:pointer; border-radius:0; box-shadow:none; font:inherit; }
109
+ .mini-link:hover { text-decoration:underline; filter:none; }
110
+ .mini-link.key-link { display:inline-grid; }
111
+ .debug { padding:0 24px 24px; }
112
+ details { border-top:1px solid var(--line); padding:16px 0 0; }
113
+ summary { cursor:pointer; color:var(--blue); font-weight:900; }
114
+ pre { overflow:auto; max-height:320px; background:#1e2924; color:#f8f0dc; border-radius:16px; padding:16px; }
115
+ .pages { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
116
+ .page-pill { border:1px solid #d9caaa; border-radius:999px; padding:5px 9px; color:var(--muted); background:#fffaf0; }
117
+ .page-pill.active { color:#fffaf0; background:var(--blue); border-color:var(--blue); }
118
+ @media (max-width: 760px) { header, .toolbar { display:block; } .actions { justify-content:flex-start; margin-top:12px; } .toggles { padding-top:0; } .template-shell { grid-template-columns:1fr; } .meta { text-align:left; margin-top:12px; } input { width:100%; } table, thead, tbody, tr, th, td { display:block; } thead { display:none; } tr { padding:12px 0; border-bottom:1px solid var(--line); } td { border:0; padding:6px 18px; } td::before { display:block; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:.08em; } td:nth-child(1)::before { content:'变量名'; } td:nth-child(2)::before { content:'key'; } td:nth-child(3)::before { content:'值'; } td:nth-child(4)::before { content:'类型'; } .unknown-group-row td::before { content:'' !important; display:none; } }
119
+ </style>
120
+ </head>
121
+ <body>
122
+ <header>
123
+ <div><h1>uniapp-miniprogram-devtool</h1><div class="muted">查看编译后小程序 key 对应的运行时变量值。</div></div>
124
+ <div class="meta"><div id="updated">-</div><div>每 300ms 自动刷新</div></div>
125
+ </header>
126
+ <main>
127
+ <section id="card" class="card">
128
+ <div class="toolbar">
129
+ <div id="status" class="status"><span class="dot"></span><span>正在启动...</span></div>
130
+ <div class="actions"><input id="filter" placeholder="筛选变量名 / key / 值"><button id="refresh" type="button">立即刷新</button><button id="reconnect" class="secondary" type="button">重新连接</button><div>页面:<span id="route" class="route">-</span></div></div>
131
+ </div>
132
+ <div class="toggles">
133
+ <div class="toggle-group">
134
+ <span class="toggle-label">视图</span>
135
+ <button id="view-business" class="secondary active" type="button">业务变量</button>
136
+ <button id="view-all" class="secondary" type="button">全部变量</button>
137
+ </div>
138
+ <div class="toggle-group">
139
+ <span class="toggle-label">变化</span>
140
+ <button id="toggle-changed" class="secondary" type="button">仅看变化项</button>
141
+ </div>
142
+ </div>
143
+ <div id="content" class="empty">等待连接微信开发者工具自动化...</div>
144
+ <div id="template-explorer" class="template-explorer"></div>
145
+ <div id="diagnostics" class="diagnostics"></div>
146
+ <div id="debug" class="debug">
147
+ <details id="debug-details">
148
+ <summary>调试信息</summary>
149
+ <div id="debug-meta"></div>
150
+ <div id="debug-pages" class="pages"></div>
151
+ <pre id="debug-pre"></pre>
152
+ </details>
153
+ </div>
154
+ </section>
155
+ </main>
156
+ <script>
157
+ window.__lastValues = window.__lastValues || {};
158
+ window.__changedKeys = window.__changedKeys || {};
159
+ window.__lastSnapshot = null;
160
+ window.__pendingDebugData = null;
161
+ window.__lastDebugFingerprint = '';
162
+ window.__lastDebugBodyText = '';
163
+ window.__refreshing = false;
164
+ window.__currentView = 'business';
165
+ window.__showChangedOnly = false;
166
+ window.__unknownRowsCollapsed = true;
167
+ window.__selectedTemplateNodeId = '';
168
+ window.__collapsedTemplateNodes = {};
169
+ window.__templateSearch = '';
170
+ window.__templateBoundOnly = false;
171
+ window.__templateChangedOnly = false;
172
+ window.__pinTemplateSelection = false;
173
+ window.__scrollTemplateSelection = false;
174
+ var cardEl = document.getElementById('card');
175
+ var statusTextEl = document.getElementById('status').lastElementChild;
176
+ var routeEl = document.getElementById('route');
177
+ var updatedEl = document.getElementById('updated');
178
+ var filterEl = document.getElementById('filter');
179
+ var contentEl = document.getElementById('content');
180
+ var templateExplorerEl = document.getElementById('template-explorer');
181
+ var diagnosticsEl = document.getElementById('diagnostics');
182
+ var debugDetailsEl = document.getElementById('debug-details');
183
+ var debugMetaEl = document.getElementById('debug-meta');
184
+ var debugPagesEl = document.getElementById('debug-pages');
185
+ var debugPreEl = document.getElementById('debug-pre');
186
+ var viewBusinessEl = document.getElementById('view-business');
187
+ var viewAllEl = document.getElementById('view-all');
188
+ var toggleChangedEl = document.getElementById('toggle-changed');
189
+ var NON_BUSINESS_KINDS = {
190
+ 'css-var': true,
191
+ 'virtual-host-class': true,
192
+ 'virtual-host-style': true,
193
+ 'virtual-host-hidden': true,
194
+ 'element-id': true,
195
+ 'static-asset': true
196
+ };
197
+ function formatValue(value) {
198
+ if (typeof value === 'undefined') return 'undefined';
199
+ if (value === null) return 'null';
200
+ if (typeof value === 'string') return value;
201
+ try { return JSON.stringify(value); } catch (error) { return String(value); }
202
+ }
203
+ function escapeHtml(value) {
204
+ return String(value).replace(/[&<>"']/g, function (ch) { return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[ch]; });
205
+ }
206
+ function matches(row, filter) {
207
+ if (!filter) return true;
208
+ var text = [row.source, row.key, row.kind, row.confidence, formatValue(row.value)].join(' ').toLowerCase();
209
+ return text.indexOf(filter.toLowerCase()) >= 0;
210
+ }
211
+ function isBusinessRow(row) {
212
+ return !NON_BUSINESS_KINDS[row.kind];
213
+ }
214
+ function isDiagnosticRow(row) {
215
+ return row.kind === 'unknown' || row.confidence === 'low' || row.confidence === 'medium';
216
+ }
217
+ function isUnknownUnknownRow(row) {
218
+ return String(row.source || '').toLowerCase() === 'unknown' && String(row.kind || '').toLowerCase() === 'unknown';
219
+ }
220
+ function renderToolbarState() {
221
+ viewBusinessEl.classList.toggle('active', window.__currentView === 'business');
222
+ viewAllEl.classList.toggle('active', window.__currentView === 'all');
223
+ toggleChangedEl.classList.toggle('active', !!window.__showChangedOnly);
224
+ toggleChangedEl.textContent = window.__showChangedOnly ? '显示全部项' : '仅看变化项';
225
+ }
226
+ function templateNodeHasBinding(node) {
227
+ if ((node.keyRefs || []).length) return true;
228
+ return (node.children || []).some(templateNodeHasBinding);
229
+ }
230
+ function templateNodeHasChanged(node, route, rowsByKey) {
231
+ if (templateNodeHasChangedBinding(node, route, rowsByKey)) return true;
232
+ return (node.children || []).some(function (child) {
233
+ return templateNodeHasChanged(child, route, rowsByKey);
234
+ });
235
+ }
236
+ function flattenTemplateNodes(node, output, depth, route, rowsByKey) {
237
+ if (!node) return;
238
+ if (window.__templateBoundOnly && !templateNodeHasBinding(node)) return;
239
+ if (window.__templateChangedOnly && !templateNodeHasChanged(node, route, rowsByKey)) return;
240
+ output.push({ node: node, depth: depth });
241
+ if (window.__collapsedTemplateNodes[node.id]) return;
242
+ (node.children || []).forEach(function (child) {
243
+ flattenTemplateNodes(child, output, depth + 1, route, rowsByKey);
244
+ });
245
+ }
246
+ function findTemplateNodeById(node, id) {
247
+ if (!node) return null;
248
+ if (node.id === id) return node;
249
+ for (var index = 0; index < (node.children || []).length; index += 1) {
250
+ var found = findTemplateNodeById(node.children[index], id);
251
+ if (found) return found;
252
+ }
253
+ return null;
254
+ }
255
+ function findTemplatePathById(node, id, ancestors) {
256
+ if (!node) return null;
257
+ if (node.id === id) return ancestors.concat(node);
258
+ for (var index = 0; index < (node.children || []).length; index += 1) {
259
+ var found = findTemplatePathById(node.children[index], id, ancestors.concat(node));
260
+ if (found) return found;
261
+ }
262
+ return null;
263
+ }
264
+ function hasChangedBinding(route, row) {
265
+ var changed = window.__changedKeys[(route || '') + ':' + row.key];
266
+ return !!(changed && Date.now() - changed.at < 1200);
267
+ }
268
+ function templateNodeHasChangedBinding(node, route, rowsByKey) {
269
+ return (node.keyRefs || []).some(function (key) {
270
+ return rowsByKey[key] && hasChangedBinding(route, rowsByKey[key]);
271
+ });
272
+ }
273
+ function expandChangedTemplateNodes(root, route, rowsByKey) {
274
+ var firstChangedNodeId = '';
275
+ function walk(node, ancestors) {
276
+ var changedHere = templateNodeHasChangedBinding(node, route, rowsByKey);
277
+ var changedInChildren = false;
278
+ (node.children || []).forEach(function (child) {
279
+ if (walk(child, ancestors.concat(node))) changedInChildren = true;
280
+ });
281
+ if (changedHere || changedInChildren) {
282
+ ancestors.forEach(function (ancestor) {
283
+ delete window.__collapsedTemplateNodes[ancestor.id];
284
+ });
285
+ delete window.__collapsedTemplateNodes[node.id];
286
+ if (!firstChangedNodeId && changedHere) firstChangedNodeId = node.id;
287
+ return true;
288
+ }
289
+ return false;
290
+ }
291
+ (root.children || []).forEach(function (child) {
292
+ walk(child, []);
293
+ });
294
+ return firstChangedNodeId;
295
+ }
296
+ function templateNodeLabel(node) {
297
+ return node.kind === 'text' ? (node.text || '#text') : '<' + node.tag + '>';
298
+ }
299
+ function templateNodeSearchText(node, rowsByKey) {
300
+ var bindingTexts = (node.keyRefs || []).map(function (key) {
301
+ var row = rowsByKey[key];
302
+ return row ? [row.source, row.key, row.expressionSummary, formatValue(row.value)].join(' ') : key;
303
+ }).join(' ');
304
+ return [templateNodeLabel(node), node.snippet || '', bindingTexts].join(' ').toLowerCase();
305
+ }
306
+ function findFirstTemplateMatch(root, query, rowsByKey) {
307
+ if (!query) return null;
308
+ function walk(node, ancestors) {
309
+ if (templateNodeSearchText(node, rowsByKey).indexOf(query) >= 0) {
310
+ return { node: node, ancestors: ancestors };
311
+ }
312
+ for (var index = 0; index < (node.children || []).length; index += 1) {
313
+ var child = node.children[index];
314
+ var found = walk(child, ancestors.concat(node));
315
+ if (found) return found;
316
+ }
317
+ return null;
318
+ }
319
+ for (var i = 0; i < (root.children || []).length; i += 1) {
320
+ var found = walk(root.children[i], []);
321
+ if (found) return found;
322
+ }
323
+ return null;
324
+ }
325
+ function findFirstTemplateNodeByKey(root, key) {
326
+ function walk(node, ancestors) {
327
+ if ((node.keyRefs || []).indexOf(key) >= 0) return { node: node, ancestors: ancestors };
328
+ for (var index = 0; index < (node.children || []).length; index += 1) {
329
+ var child = node.children[index];
330
+ var found = walk(child, ancestors.concat(node));
331
+ if (found) return found;
332
+ }
333
+ return null;
334
+ }
335
+ for (var i = 0; i < (root.children || []).length; i += 1) {
336
+ var found = walk(root.children[i], []);
337
+ if (found) return found;
338
+ }
339
+ return null;
340
+ }
341
+ function selectTemplateNodeByKey(snapshot, key) {
342
+ if (!snapshot || !snapshot.templateTree) return;
343
+ var found = findFirstTemplateNodeByKey(snapshot.templateTree, key);
344
+ if (!found) return;
345
+ window.__selectedTemplateNodeId = found.node.id;
346
+ found.ancestors.forEach(function (ancestor) {
347
+ delete window.__collapsedTemplateNodes[ancestor.id];
348
+ });
349
+ window.__scrollTemplateSelection = true;
350
+ render(snapshot, true);
351
+ }
352
+ function countChangedTemplateNodes(nodes, route, rowsByKey) {
353
+ var total = 0;
354
+ nodes.forEach(function (node) {
355
+ if (templateNodeHasChangedBinding(node, route, rowsByKey)) total += 1;
356
+ });
357
+ return total;
358
+ }
359
+ function renderTemplateExplorer(data) {
360
+ var root = data.templateTree;
361
+ if (!root || !(root.children || []).length) {
362
+ templateExplorerEl.innerHTML = '<div class="template-shell"><div class="template-tree"><div class="template-pane-title">模板树</div><div class="template-pane-body muted">当前页面没有可解析的模板结构。</div></div><div class="template-detail"><div class="template-pane-title">节点详情</div><div class="template-pane-body muted">选择模板节点后可查看关联变量与当前值。</div></div></div>';
363
+ return;
364
+ }
365
+ if (!window.__selectedTemplateNodeId) {
366
+ window.__selectedTemplateNodeId = root.children[0] ? root.children[0].id : root.id;
367
+ }
368
+ var rowsByKey = {};
369
+ (data.rows || []).forEach(function (row) {
370
+ rowsByKey[row.key] = row;
371
+ });
372
+ var searchQuery = String(window.__templateSearch || '').trim().toLowerCase();
373
+ var matched = findFirstTemplateMatch(root, searchQuery, rowsByKey);
374
+ if (matched && !window.__pinTemplateSelection) {
375
+ window.__selectedTemplateNodeId = matched.node.id;
376
+ matched.ancestors.forEach(function (ancestor) {
377
+ delete window.__collapsedTemplateNodes[ancestor.id];
378
+ });
379
+ }
380
+ var flattened = [];
381
+ root.children.forEach(function (child) {
382
+ flattenTemplateNodes(child, flattened, 0, data.route, rowsByKey);
383
+ });
384
+ var selectedNode = findTemplateNodeById(root, window.__selectedTemplateNodeId) || root.children[0] || root;
385
+ if (flattened.length && !flattened.some(function (item) { return item.node.id === selectedNode.id; })) {
386
+ selectedNode = flattened[0].node;
387
+ }
388
+ window.__selectedTemplateNodeId = selectedNode.id;
389
+ var visibleNodes = flattened.map(function (item) { return item.node; });
390
+ var selectedPath = findTemplatePathById(root, selectedNode.id, []) || [selectedNode];
391
+ var breadcrumbNodes = selectedPath.filter(function (node) { return node.id !== 'root'; });
392
+ var selectedNodeIndex = visibleNodes.findIndex(function (node) { return node.id === selectedNode.id; });
393
+ var prevNode = selectedNodeIndex > 0 ? visibleNodes[selectedNodeIndex - 1] : null;
394
+ var nextNode = selectedNodeIndex >= 0 && selectedNodeIndex < visibleNodes.length - 1 ? visibleNodes[selectedNodeIndex + 1] : null;
395
+ var hasAnyChangedNode = visibleNodes.some(function (node) { return templateNodeHasChangedBinding(node, data.route, rowsByKey); });
396
+ var changedNodeCount = countChangedTemplateNodes(visibleNodes, data.route, rowsByKey);
397
+ var detailRows = (selectedNode.keyRefs || []).map(function (key) {
398
+ return rowsByKey[key];
399
+ }).filter(Boolean);
400
+ templateExplorerEl.innerHTML = '<div class="template-shell">' +
401
+ '<div class="template-tree">' +
402
+ '<div class="template-pane-title">模板树</div>' +
403
+ '<div class="template-pane-body">' +
404
+ '<div class="template-tools">' +
405
+ '<input class="template-search with-tools" data-role="template-search" placeholder="搜索标签 / 片段 / 变量" value="' + escapeHtml(window.__templateSearch || '') + '">' +
406
+ '<button class="secondary ' + (window.__templateBoundOnly ? 'active' : '') + '" data-role="toggle-bound-only" type="button">' + (window.__templateBoundOnly ? '显示全部节点' : '仅显示有绑定节点') + '</button>' +
407
+ '<button class="secondary ' + (window.__templateChangedOnly ? 'active' : '') + '" data-role="toggle-changed-only" type="button">' + (window.__templateChangedOnly ? '显示全部变化范围' : '仅显示变化节点') + '</button>' +
408
+ '<button class="secondary ' + (window.__pinTemplateSelection ? 'active' : '') + '" data-role="toggle-pin-selection" type="button">' + (window.__pinTemplateSelection ? '取消固定选中' : '固定当前选中') + '</button>' +
409
+ '<button class="secondary" data-role="expand-changed" type="button"' + (hasAnyChangedNode ? '' : ' disabled') + '>展开变化节点</button>' +
410
+ '</div>' +
411
+ '<div class="template-summary">当前可见节点 ' + visibleNodes.length + ' 个,变化节点 ' + changedNodeCount + ' 个。</div>' +
412
+ '<div class="template-list">' + flattened.map(function (item) {
413
+ var node = item.node;
414
+ var depth = item.depth;
415
+ var isSelected = node.id === selectedNode.id;
416
+ var hasChildren = !!((node.children || []).length);
417
+ var bindingCount = (node.keyRefs || []).length;
418
+ var hasChange = (node.keyRefs || []).some(function (key) { return rowsByKey[key] && hasChangedBinding(data.route, rowsByKey[key]); });
419
+ var isMatch = searchQuery && templateNodeSearchText(node, rowsByKey).indexOf(searchQuery) >= 0;
420
+ var title = templateNodeLabel(node);
421
+ return '<button class="template-node ' + (isSelected ? 'active ' : '') + (hasChange ? 'template-highlight ' : '') + (isMatch ? 'match' : '') + '" type="button" data-role="select-node" data-node-id="' + escapeHtml(node.id) + '">' +
422
+ '<span class="template-node-row">' +
423
+ '<span class="template-indent" style="--depth:' + depth + ';"></span>' +
424
+ '<span class="template-toggle ' + (hasChildren ? '' : 'placeholder') + '" data-role="' + (hasChildren ? 'toggle-node' : '') + '" data-node-id="' + escapeHtml(node.id) + '">' + (hasChildren ? (window.__collapsedTemplateNodes[node.id] ? '+' : '-') : '+') + '</span>' +
425
+ '<span class="template-tag">' + escapeHtml(title) + '</span>' +
426
+ '<span class="template-kind">' + escapeHtml(node.kind) + '</span>' +
427
+ '<span class="template-count">' + bindingCount + ' 个绑定</span>' +
428
+ '</span>' +
429
+ '</button>';
430
+ }).join('') + '</div>' +
431
+ (!flattened.length ? '<div class="muted">当前筛选下没有可展示的模板节点。</div>' : '') +
432
+ (searchQuery && !matched ? '<div class="muted">没有找到匹配节点。</div>' : '') +
433
+ (searchQuery && matched && window.__pinTemplateSelection ? '<div class="muted">已固定当前选中节点,搜索结果仅高亮不自动跳转。</div>' : '') +
434
+ '</div>' +
435
+ '</div>' +
436
+ '<div class="template-detail">' +
437
+ '<div class="template-pane-title">节点详情</div>' +
438
+ '<div class="template-pane-body"><div class="template-detail-grid">' +
439
+ '<div class="template-detail-block"><div><button class="secondary" type="button" data-role="select-node" data-node-id="' + escapeHtml(prevNode ? prevNode.id : '') + '"' + (prevNode ? '' : ' disabled') + '>上一个节点</button> <button class="secondary" type="button" data-role="select-node" data-node-id="' + escapeHtml(nextNode ? nextNode.id : '') + '"' + (nextNode ? '' : ' disabled') + '>下一个节点</button></div></div>' +
440
+ '<div class="template-detail-block"><div class="template-detail-label">节点</div><div>' + escapeHtml(templateNodeLabel(selectedNode)) + '</div></div>' +
441
+ '<div class="template-detail-block"><div class="template-detail-label">路径</div><div class="template-breadcrumb">' + breadcrumbNodes.map(function (node, index) {
442
+ return '<span class="template-crumb"><button class="mini-link" type="button" data-role="select-node" data-node-id="' + escapeHtml(node.id) + '">' + escapeHtml(templateNodeLabel(node)) + '</button>' + (index < breadcrumbNodes.length - 1 ? '<span class="template-sep">/</span>' : '') + '</span>';
443
+ }).join('') + '</div></div>' +
444
+ '<div class="template-detail-block"><div class="template-detail-label">模板片段</div><code>' + escapeHtml(selectedNode.snippet || '-') + '</code></div>' +
445
+ '<div class="template-detail-block"><div class="template-detail-label">属性</div><div>' + ((selectedNode.attrs || []).length ? selectedNode.attrs.map(function (attr) { return '<code>' + escapeHtml(attr.name + (attr.value ? '="' + attr.value + '"' : '')) + '</code>'; }).join(' ') : '<span class="muted">无</span>') + '</div></div>' +
446
+ '<div class="template-detail-block"><div class="template-detail-label">关联变量</div><div class="template-bindings">' + (detailRows.length ? detailRows.map(function (row) {
447
+ var snippets = (row.wxmlUsages || []).slice(0, 2).map(function (snippet) { return '<span class="snippet">' + escapeHtml(snippet) + '</span>'; }).join('');
448
+ return '<div class="binding-card">' +
449
+ '<div class="binding-head"><strong><button class="mini-link" type="button" data-role="locate-key" data-key="' + escapeHtml(row.key) + '">' + escapeHtml(row.source) + '</button></strong><span class="muted"><button class="mini-link" type="button" data-role="locate-key" data-key="' + escapeHtml(row.key) + '">' + escapeHtml(row.key) + '</button> / ' + escapeHtml(row.confidence) + '</span></div>' +
450
+ '<div class="binding-value">' + escapeHtml(formatValue(row.value)) + '</div>' +
451
+ '<div class="muted">' + escapeHtml(row.expressionSummary || '-') + '</div>' +
452
+ (snippets ? '<div class="binding-snippets">' + snippets + '</div>' : '') +
453
+ '</div>';
454
+ }).join('') : '<div class="muted">当前节点没有关联到可识别的运行时变量。</div>') + '</div></div>' +
455
+ '<div class="template-detail-block"><div class="template-detail-label">该节点变量表</div>' + (detailRows.length ? '<div class="binding-table-wrap"><table class="binding-table"><thead><tr><th>变量名</th><th>Key</th><th>值</th><th>类型</th></tr></thead><tbody>' + detailRows.map(function (row) {
456
+ var changed = hasChangedBinding(data.route, row);
457
+ var diff = changed ? window.__changedKeys[(data.route || '') + ':' + row.key] : null;
458
+ return '<tr>' +
459
+ '<td><strong><button class="mini-link" type="button" data-role="locate-key" data-key="' + escapeHtml(row.key) + '">' + escapeHtml(row.source) + '</button></strong><div class="muted">' + escapeHtml(row.confidence) + '</div></td>' +
460
+ '<td><button class="mini-link key-link" type="button" data-role="locate-key" data-key="' + escapeHtml(row.key) + '"><span class="key">' + escapeHtml(row.key) + '</span></button></td>' +
461
+ '<td>' + escapeHtml(formatValue(row.value)) + (diff ? '<div class="diff">' + escapeHtml(diff.from) + ' -> ' + escapeHtml(diff.to) + '</div>' : '') + '</td>' +
462
+ '<td>' + escapeHtml(row.kind) + '</td>' +
463
+ '</tr>';
464
+ }).join('') + '</tbody></table></div>' : '<div class="muted">当前节点没有可展示的变量表。</div>') + '</div>' +
465
+ '</div></div>' +
466
+ '</div>' +
467
+ '</div>';
468
+ if (window.__scrollTemplateSelection) {
469
+ window.__scrollTemplateSelection = false;
470
+ var activeNodeEl = templateExplorerEl.querySelector('.template-node.active');
471
+ activeNodeEl && activeNodeEl.scrollIntoView && activeNodeEl.scrollIntoView({ block: 'nearest' });
472
+ }
473
+ }
474
+ function renderDebug(data, force) {
475
+ var debug = data.debug || {};
476
+ var pages = debug.keymapPages || [];
477
+ var active = data.route || '';
478
+ var fingerprint = JSON.stringify({
479
+ status: data.status || '',
480
+ route: active,
481
+ pageId: debug.pageId == null ? null : debug.pageId,
482
+ rawRoute: debug.rawRoute || '',
483
+ keymapPages: pages
484
+ });
485
+ window.__pendingDebugData = data;
486
+ var debugChanged = fingerprint !== window.__lastDebugFingerprint;
487
+ if (debugChanged || force) {
488
+ window.__lastDebugFingerprint = fingerprint;
489
+ debugMetaEl.innerHTML =
490
+ '<div class="muted">pageId: ' + escapeHtml(debug.pageId == null ? '-' : debug.pageId) + '</div>' +
491
+ '<div class="muted">rawRoute: ' + escapeHtml(debug.rawRoute || '-') + '</div>' +
492
+ '<div class="muted">status: ' + escapeHtml(data.status || '-') + '</div>';
493
+ debugPagesEl.innerHTML = pages.map(function (page) {
494
+ return '<span class="page-pill ' + (page === active ? 'active' : '') + '">' + escapeHtml(page) + '</span>';
495
+ }).join('');
496
+ } else if (!debugDetailsEl.open) {
497
+ return;
498
+ }
499
+ if (debugDetailsEl.open) {
500
+ var debugBodyText = JSON.stringify({ debug: debug, rawData: data.rawData || {} }, null, 2);
501
+ if (debugBodyText !== window.__lastDebugBodyText) {
502
+ debugPreEl.textContent = debugBodyText;
503
+ window.__lastDebugBodyText = debugBodyText;
504
+ }
505
+ } else {
506
+ if (window.__lastDebugBodyText !== '展开调试信息后可查看原始页面数据。') {
507
+ debugPreEl.textContent = '展开调试信息后可查看原始页面数据。';
508
+ window.__lastDebugBodyText = '展开调试信息后可查看原始页面数据。';
509
+ }
510
+ }
511
+ }
512
+ function renderDiagnostics(data, rows) {
513
+ var diagnosticRows = rows.filter(isDiagnosticRow);
514
+ if (!diagnosticRows.length) {
515
+ diagnosticsEl.innerHTML = '<div class="diagnostics-card"><div class="diagnostic-head"><div class="diagnostic-title">低置信度 / 未识别诊断</div><div class="diagnostic-meta">当前页面没有需要额外诊断的项</div></div></div>';
516
+ return;
517
+ }
518
+ diagnosticsEl.innerHTML = '<div class="diagnostics-card">' +
519
+ '<div class="diagnostic-head">' +
520
+ '<div class="diagnostic-title">低置信度 / 未识别诊断</div>' +
521
+ '<div class="diagnostic-meta">当前页面共 ' + diagnosticRows.length + ' 项,建议优先检查 kind=unknown 或 confidence=low 的项</div>' +
522
+ '</div>' +
523
+ '<div class="diagnostics-grid">' + diagnosticRows.map(function (row) {
524
+ var usages = (row.wxmlUsages || []).slice(0, 3).map(function (snippet) {
525
+ return '<span class="snippet">' + escapeHtml(snippet) + '</span>';
526
+ }).join('');
527
+ return '<div class="diagnostic-item">' +
528
+ '<div class="diagnostic-head">' +
529
+ '<div class="diagnostic-title">' + escapeHtml(row.key) + ' -> ' + escapeHtml(row.source || 'unknown') + '</div>' +
530
+ '<div class="diagnostic-meta">kind: ' + escapeHtml(row.kind) + ' / confidence: ' + escapeHtml(row.confidence) + '</div>' +
531
+ '</div>' +
532
+ '<div class="diagnostic-expression">表达式:' + escapeHtml(row.expressionSummary || '-') + '</div>' +
533
+ '<div class="diagnostic-expression">当前值:' + escapeHtml(formatValue(row.value)) + '</div>' +
534
+ (usages ? '<div class="diagnostic-usages">' + usages + '</div>' : '') +
535
+ '</div>';
536
+ }).join('') + '</div>' +
537
+ '</div>';
538
+ }
539
+ function renderRuntimeRow(data, row, now) {
540
+ var id = (data.route || '') + ':' + row.key;
541
+ var changed = window.__changedKeys[id] && now - window.__changedKeys[id].at < 1200;
542
+ var diff = changed ? '<div class="diff">' + escapeHtml(window.__changedKeys[id].from) + ' -> ' + escapeHtml(window.__changedKeys[id].to) + '</div>' : '';
543
+ return '<tr class="' + (changed ? 'flash' : '') + '"><td><strong><button class="mini-link" type="button" data-role="locate-key" data-key="' + escapeHtml(row.key) + '">' + escapeHtml(row.source) + '</button></strong><div class="muted">' + escapeHtml(row.confidence) + '</div></td><td><button class="mini-link key-link" type="button" data-role="locate-key" data-key="' + escapeHtml(row.key) + '"><span class="key">' + escapeHtml(row.key) + '</span></button></td><td class="value">' + escapeHtml(formatValue(row.value)) + diff + '</td><td>' + escapeHtml(row.kind) + '</td></tr>';
544
+ }
545
+ function renderUnknownGroupRow(data, rows, now) {
546
+ var changedCount = rows.filter(function (row) {
547
+ var changed = window.__changedKeys[(data.route || '') + ':' + row.key];
548
+ return !!(changed && now - changed.at < 1200);
549
+ }).length;
550
+ var sampleKeys = rows.slice(0, 8).map(function (row) { return row.key; }).join(', ');
551
+ var meta = rows.length + ' 项' + (changedCount ? ' / ' + changedCount + ' 项有变化' : '') + (sampleKeys ? ' / ' + sampleKeys : '');
552
+ return '<tr class="unknown-group-row">' +
553
+ '<td colspan="4"><button class="mini-link unknown-group-toggle" type="button" data-role="toggle-unknown-group">' +
554
+ '<span>' + (window.__unknownRowsCollapsed ? '+' : '-') + ' unknown 变量集合</span>' +
555
+ '<span class="unknown-group-meta">' + escapeHtml(meta) + '</span>' +
556
+ '</button></td>' +
557
+ '</tr>';
558
+ }
559
+ function render(data, force) {
560
+ var filter = filterEl.value || '';
561
+ var allRows = data.rows || [];
562
+ cardEl.className = 'card' + (data.connected ? ' connected' : '');
563
+ statusTextEl.textContent = data.status || (data.connected ? '已连接' : '未连接');
564
+ routeEl.textContent = data.route || '-';
565
+ updatedEl.textContent = data.updatedAt ? new Date(data.updatedAt).toLocaleTimeString() : '-';
566
+ renderToolbarState();
567
+ renderDebug(data, force);
568
+ renderTemplateExplorer(data);
569
+ var now = Date.now();
570
+ allRows.forEach(function (row) {
571
+ var id = (data.route || '') + ':' + row.key;
572
+ var current = formatValue(row.value);
573
+ if (Object.prototype.hasOwnProperty.call(window.__lastValues, id) && window.__lastValues[id] !== current) {
574
+ window.__changedKeys[id] = { at: now, from: window.__lastValues[id], to: current };
575
+ }
576
+ window.__lastValues[id] = current;
577
+ });
578
+ var rows = allRows
579
+ .filter(function (row) { return window.__currentView === 'all' ? true : isBusinessRow(row); })
580
+ .filter(function (row) {
581
+ if (!window.__showChangedOnly) return true;
582
+ var changed = window.__changedKeys[(data.route || '') + ':' + row.key];
583
+ return !!(changed && now - changed.at < 1200);
584
+ })
585
+ .filter(function (row) { return matches(row, filter); });
586
+ var fingerprint = JSON.stringify({
587
+ connected: data.connected,
588
+ status: data.status,
589
+ route: data.route,
590
+ rows: rows,
591
+ filter: filter,
592
+ view: window.__currentView,
593
+ changedOnly: window.__showChangedOnly,
594
+ unknownRowsCollapsed: window.__unknownRowsCollapsed
595
+ });
596
+ renderDiagnostics(data, allRows);
597
+ if (!force && fingerprint === window.__lastUniappxFingerprint) return;
598
+ window.__lastUniappxFingerprint = fingerprint;
599
+ if (!rows.length) {
600
+ contentEl.className = 'empty';
601
+ contentEl.textContent = data.connected ? '当前筛选条件下没有可展示的运行时变量。' : '等待连接微信开发者工具自动化...';
602
+ return;
603
+ }
604
+ contentEl.className = '';
605
+ var knownRows = rows.filter(function (row) { return !isUnknownUnknownRow(row); });
606
+ var unknownRows = rows.filter(isUnknownUnknownRow);
607
+ var bodyHtml = knownRows.map(function (row) { return renderRuntimeRow(data, row, now); }).join('');
608
+ if (unknownRows.length) {
609
+ bodyHtml += renderUnknownGroupRow(data, unknownRows, now);
610
+ if (!window.__unknownRowsCollapsed) {
611
+ bodyHtml += unknownRows.map(function (row) { return renderRuntimeRow(data, row, now); }).join('');
612
+ }
613
+ }
614
+ contentEl.innerHTML = '<table><thead><tr><th>变量名</th><th>Key</th><th>值</th><th>类型</th></tr></thead><tbody>' + bodyHtml + '</tbody></table>';
615
+ }
616
+ async function refresh(force) {
617
+ if (window.__refreshing) return;
618
+ window.__refreshing = true;
619
+ try {
620
+ var res = await fetch('/api/snapshot?ts=' + Date.now(), { cache: 'no-store' });
621
+ window.__lastSnapshot = await res.json();
622
+ render(window.__lastSnapshot, !!force);
623
+ } catch (error) {
624
+ statusTextEl.textContent = '面板连接已断开';
625
+ } finally {
626
+ window.__refreshing = false;
627
+ }
628
+ }
629
+ async function reconnect() {
630
+ statusTextEl.textContent = '正在重新连接...';
631
+ try {
632
+ await fetch('/api/reconnect', { method: 'POST', cache: 'no-store' });
633
+ } catch (error) {}
634
+ window.__lastUniappxFingerprint = '';
635
+ window.__lastDebugFingerprint = '';
636
+ refresh(true);
637
+ }
638
+ debugDetailsEl.addEventListener('toggle', function () {
639
+ if (!debugDetailsEl.open || !window.__pendingDebugData) return;
640
+ var data = window.__pendingDebugData;
641
+ var debugBodyText = JSON.stringify({ debug: data.debug || {}, rawData: data.rawData || {} }, null, 2);
642
+ debugPreEl.textContent = debugBodyText;
643
+ window.__lastDebugBodyText = debugBodyText;
644
+ });
645
+ document.getElementById('refresh').addEventListener('click', function () {
646
+ window.__lastUniappxFingerprint = '';
647
+ window.__lastDebugFingerprint = '';
648
+ refresh(true);
649
+ });
650
+ document.getElementById('reconnect').addEventListener('click', reconnect);
651
+ contentEl.addEventListener('click', function (event) {
652
+ var target = event.target;
653
+ while (target && target !== contentEl) {
654
+ var role = target.getAttribute && target.getAttribute('data-role');
655
+ var key = target.getAttribute && target.getAttribute('data-key');
656
+ if (role === 'toggle-unknown-group') {
657
+ window.__unknownRowsCollapsed = !window.__unknownRowsCollapsed;
658
+ window.__lastUniappxFingerprint = '';
659
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
660
+ return;
661
+ }
662
+ if (role === 'locate-key' && key) {
663
+ selectTemplateNodeByKey(window.__lastSnapshot, key);
664
+ return;
665
+ }
666
+ target = target.parentNode;
667
+ }
668
+ });
669
+ templateExplorerEl.addEventListener('click', function (event) {
670
+ var target = event.target;
671
+ while (target && target !== templateExplorerEl) {
672
+ var role = target.getAttribute && target.getAttribute('data-role');
673
+ var nodeId = target.getAttribute && target.getAttribute('data-node-id');
674
+ var key = target.getAttribute && target.getAttribute('data-key');
675
+ if (role === 'toggle-node' && nodeId) {
676
+ window.__collapsedTemplateNodes[nodeId] = !window.__collapsedTemplateNodes[nodeId];
677
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
678
+ return;
679
+ }
680
+ if (role === 'expand-changed') {
681
+ if (window.__lastSnapshot && window.__lastSnapshot.templateTree) {
682
+ var rowsByKey = {};
683
+ (window.__lastSnapshot.rows || []).forEach(function (row) {
684
+ rowsByKey[row.key] = row;
685
+ });
686
+ var firstChangedNodeId = expandChangedTemplateNodes(window.__lastSnapshot.templateTree, window.__lastSnapshot.route, rowsByKey);
687
+ if (firstChangedNodeId) window.__selectedTemplateNodeId = firstChangedNodeId;
688
+ window.__scrollTemplateSelection = true;
689
+ render(window.__lastSnapshot, true);
690
+ }
691
+ return;
692
+ }
693
+ if (role === 'toggle-bound-only') {
694
+ window.__templateBoundOnly = !window.__templateBoundOnly;
695
+ window.__scrollTemplateSelection = true;
696
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
697
+ return;
698
+ }
699
+ if (role === 'toggle-changed-only') {
700
+ window.__templateChangedOnly = !window.__templateChangedOnly;
701
+ window.__scrollTemplateSelection = true;
702
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
703
+ return;
704
+ }
705
+ if (role === 'toggle-pin-selection') {
706
+ window.__pinTemplateSelection = !window.__pinTemplateSelection;
707
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
708
+ return;
709
+ }
710
+ if (role === 'locate-key' && key) {
711
+ selectTemplateNodeByKey(window.__lastSnapshot, key);
712
+ return;
713
+ }
714
+ if (role === 'select-node' && nodeId) {
715
+ window.__selectedTemplateNodeId = nodeId;
716
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
717
+ return;
718
+ }
719
+ target = target.parentNode;
720
+ }
721
+ });
722
+ templateExplorerEl.addEventListener('input', function (event) {
723
+ var target = event.target;
724
+ if (target && target.getAttribute && target.getAttribute('data-role') === 'template-search') {
725
+ window.__templateSearch = target.value || '';
726
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
727
+ }
728
+ });
729
+ viewBusinessEl.addEventListener('click', function () {
730
+ window.__currentView = 'business';
731
+ window.__lastUniappxFingerprint = '';
732
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
733
+ });
734
+ viewAllEl.addEventListener('click', function () {
735
+ window.__currentView = 'all';
736
+ window.__lastUniappxFingerprint = '';
737
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
738
+ });
739
+ toggleChangedEl.addEventListener('click', function () {
740
+ window.__showChangedOnly = !window.__showChangedOnly;
741
+ window.__lastUniappxFingerprint = '';
742
+ if (window.__lastSnapshot) render(window.__lastSnapshot, true);
743
+ });
744
+ filterEl.addEventListener('input', function () { if (window.__lastSnapshot) render(window.__lastSnapshot, true); });
745
+ renderToolbarState();
746
+ refresh(true);
747
+ setInterval(function () { refresh(false); }, 300);
748
+ <\/script>
749
+ </body>
750
+ </html>`;
751
+ }
752
+ function openBrowser(url) {
753
+ if (process.env.UNIAPP_MINIPROGRAM_DEVTOOL_NO_OPEN === "1" || process.env.UNIAPPX_KEYMAP_NO_OPEN === "1") return;
754
+ if (process.platform === "darwin") (0, node_child_process.spawn)("open", [url], {
755
+ detached: true,
756
+ stdio: "ignore"
757
+ }).unref();
758
+ }
759
+ function defaultWebPanelPort() {
760
+ return Number(process.env.UNIAPP_MINIPROGRAM_DEVTOOL_PORT || process.env.UNIAPPX_KEYMAP_PORT || 17890);
761
+ }
762
+ async function listen(server, preferredPort) {
763
+ if (preferredPort <= 0) {
764
+ await new Promise((resolve, reject) => {
765
+ const onError = (error) => {
766
+ server.off("listening", onListening);
767
+ reject(error);
768
+ };
769
+ const onListening = () => {
770
+ server.off("error", onError);
771
+ resolve();
772
+ };
773
+ server.once("error", onError);
774
+ server.once("listening", onListening);
775
+ server.listen(0, "127.0.0.1");
776
+ });
777
+ const address = server.address();
778
+ if (!address || typeof address === "string") throw new Error("无法解析本地 Web 面板端口。");
779
+ return address.port;
780
+ }
781
+ for (let offset = 0; offset < 20; offset += 1) {
782
+ const port = preferredPort + offset;
783
+ try {
784
+ await new Promise((resolve, reject) => {
785
+ const onError = (error) => {
786
+ server.off("listening", onListening);
787
+ reject(error);
788
+ };
789
+ const onListening = () => {
790
+ server.off("error", onError);
791
+ resolve();
792
+ };
793
+ server.once("error", onError);
794
+ server.once("listening", onListening);
795
+ server.listen(port, "127.0.0.1");
796
+ });
797
+ return port;
798
+ } catch (error) {
799
+ const code = error.code;
800
+ if (code !== "EADDRINUSE" && code !== "EACCES" && code !== "EPERM") throw error;
801
+ }
802
+ }
803
+ throw new Error(`未找到可用的本地 Web 面板端口,尝试范围:${preferredPort} - ${preferredPort + 19}`);
804
+ }
805
+ async function startWebPanel(options = defaultWebPanelPort()) {
806
+ const normalized = typeof options === "number" ? { port: options } : options;
807
+ const preferredPort = normalized.port ?? defaultWebPanelPort();
808
+ let snapshot = { ...DEFAULT_SNAPSHOT };
809
+ const server = node_http.default.createServer((req, res) => {
810
+ if (req.url?.startsWith("/api/snapshot")) {
811
+ res.writeHead(200, {
812
+ "content-type": "application/json; charset=utf-8",
813
+ "cache-control": "no-store"
814
+ });
815
+ res.end(JSON.stringify(snapshot));
816
+ return;
817
+ }
818
+ if (req.url?.startsWith("/api/reconnect")) {
819
+ if (req.method !== "POST") {
820
+ res.writeHead(405, {
821
+ "content-type": "application/json; charset=utf-8",
822
+ "cache-control": "no-store"
823
+ });
824
+ res.end(JSON.stringify({
825
+ ok: false,
826
+ error: "请求方法不支持"
827
+ }));
828
+ return;
829
+ }
830
+ snapshot = {
831
+ ...snapshot,
832
+ connected: false,
833
+ status: "已发起重新连接...",
834
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
835
+ };
836
+ Promise.resolve(normalized.onReconnect?.()).then(() => {
837
+ res.writeHead(202, {
838
+ "content-type": "application/json; charset=utf-8",
839
+ "cache-control": "no-store"
840
+ });
841
+ res.end(JSON.stringify({ ok: true }));
842
+ }).catch((error) => {
843
+ const message = error instanceof Error ? error.message : String(error);
844
+ snapshot = {
845
+ ...snapshot,
846
+ connected: false,
847
+ status: `重新连接失败:${message}`,
848
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
849
+ };
850
+ res.writeHead(500, {
851
+ "content-type": "application/json; charset=utf-8",
852
+ "cache-control": "no-store"
853
+ });
854
+ res.end(JSON.stringify({
855
+ ok: false,
856
+ error: message
857
+ }));
858
+ });
859
+ return;
860
+ }
861
+ res.writeHead(200, {
862
+ "content-type": "text/html; charset=utf-8",
863
+ "cache-control": "no-store"
864
+ });
865
+ res.end(html());
866
+ });
867
+ const url = `http://127.0.0.1:${await listen(server, preferredPort)}`;
868
+ openBrowser(url);
869
+ return {
870
+ url,
871
+ update(next) {
872
+ snapshot = next;
873
+ },
874
+ close() {
875
+ server.close();
876
+ }
877
+ };
878
+ }
879
+ //#endregion
880
+ exports.startWebPanel = startWebPanel;
881
+
882
+ //# sourceMappingURL=web-panel.cjs.map