pug-site-core 2.0.22 → 3.0.0

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,2104 @@
1
+ /**
2
+ * Pug 调试工具模块
3
+ * 为页面注入调试功能,允许用户点击元素查看调试信息
4
+ */
5
+
6
+ /**
7
+ * 调试工具核心JavaScript代码
8
+ * 提取到独立函数以便于维护和获得语法提示
9
+ */
10
+ function createDebugToolScript() {
11
+ /**
12
+ * 公共样式配置
13
+ */
14
+ const DEBUG_STYLES = {
15
+ // 通用按钮样式
16
+ button: {
17
+ base: `
18
+ border: none;
19
+ cursor: pointer;
20
+ border-radius: 4px;
21
+ font-size: 12px;
22
+ padding: 6px 12px;
23
+ transition: background-color 0.2s;
24
+ `,
25
+ primary: 'background: #4CAF50; color: white;',
26
+ secondary: 'background: #FF9800; color: white;',
27
+ danger: 'background: #f44336; color: white;',
28
+ neutral: 'background: #757575; color: white;'
29
+ },
30
+
31
+ // 输入框样式
32
+ input: `
33
+ width: 100%;
34
+ background: #333 !important;
35
+ color: white !important;
36
+ border: 1px solid #555;
37
+ border-radius: 3px;
38
+ padding: 5px;
39
+ font-size: 11px;
40
+ box-sizing: border-box;
41
+ -webkit-appearance: none;
42
+ -webkit-box-shadow: inset 0 0 0 1000px #333 !important;
43
+ -webkit-text-fill-color: white !important;
44
+ `,
45
+
46
+ // 标签样式
47
+ label: `
48
+ color: #FFD700;
49
+ font-size: 11px;
50
+ display: block;
51
+ margin-bottom: 3px;
52
+ `,
53
+
54
+ // 弹窗头部样式
55
+ headerStyle: `
56
+ display: flex;
57
+ justify-content: space-between;
58
+ align-items: center;
59
+ padding: 10px 15px;
60
+ background: rgba(255, 255, 255, 0.1);
61
+ border-radius: 8px 8px 0 0;
62
+ cursor: move;
63
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
64
+ `,
65
+
66
+ // 弹窗内容样式
67
+ contentStyle: `
68
+ padding: 15px;
69
+ overflow-y: auto;
70
+ max-height: calc(95vh - 50px);
71
+ `
72
+ };
73
+
74
+ /**
75
+ * UI工具类
76
+ */
77
+ class UIHelper {
78
+ /**
79
+ * 创建带样式的元素
80
+ */
81
+ static createElement(tag, id = '', className = '', styles = '', innerHTML = '') {
82
+ const element = document.createElement(tag);
83
+ if (id) element.id = id;
84
+ if (className) element.className = className;
85
+ if (styles) element.style.cssText = styles;
86
+ if (innerHTML) element.innerHTML = innerHTML;
87
+ return element;
88
+ }
89
+
90
+ /**
91
+ * 创建按钮
92
+ */
93
+ static createButton(id, text, type = 'primary', onClick = null) {
94
+ const button = this.createElement(
95
+ 'button',
96
+ id,
97
+ 'pug-debug-element',
98
+ DEBUG_STYLES.button.base + DEBUG_STYLES.button[type],
99
+ text
100
+ );
101
+ if (onClick) button.addEventListener('click', onClick);
102
+ return button;
103
+ }
104
+
105
+ /**
106
+ * 创建输入框组
107
+ */
108
+ static createInputGroup(label, id, value = '', placeholder = '', type = 'text') {
109
+ const container = this.createElement('div');
110
+
111
+ const labelEl = this.createElement('label', '', '', DEBUG_STYLES.label, label);
112
+ labelEl.setAttribute('for', id);
113
+
114
+ const input = this.createElement('input', id, '', DEBUG_STYLES.input);
115
+ input.type = type;
116
+ input.value = value;
117
+ input.placeholder = placeholder;
118
+ input.setAttribute('autocomplete', 'off');
119
+ input.setAttribute('spellcheck', 'false');
120
+
121
+ container.appendChild(labelEl);
122
+ container.appendChild(input);
123
+ return { container, input };
124
+ }
125
+
126
+ /**
127
+ * 创建选择框组
128
+ */
129
+ static createSelectGroup(label, id, options, currentValue = '') {
130
+ const container = this.createElement('div');
131
+
132
+ const labelEl = this.createElement('label', '', '', DEBUG_STYLES.label, label);
133
+ labelEl.setAttribute('for', id);
134
+
135
+ const select = this.createElement('select', id, '', `
136
+ width: 100%;
137
+ background: #333;
138
+ color: white;
139
+ border: 1px solid #555;
140
+ border-radius: 3px;
141
+ padding: 5px;
142
+ font-size: 11px;
143
+ box-sizing: border-box;
144
+ `);
145
+
146
+ options.forEach(({ value, text, selected }) => {
147
+ const option = this.createElement('option', '', '', '', text);
148
+ option.value = value;
149
+ if (selected || value === currentValue) option.selected = true;
150
+ });
151
+
152
+ container.appendChild(labelEl);
153
+ container.appendChild(select);
154
+ return { container, select };
155
+ }
156
+
157
+ /**
158
+ * 显示提示消息
159
+ */
160
+ static showToast(message, type = 'info') {
161
+ // 移除现有提示
162
+ const existing = document.getElementById('pug-debug-toast');
163
+ if (existing) existing.remove();
164
+
165
+ const colors = {
166
+ success: '#4CAF50',
167
+ error: '#f44336',
168
+ info: '#2196F3'
169
+ };
170
+
171
+ const toast = this.createElement('div', 'pug-debug-toast', 'pug-debug-toast', `
172
+ position: fixed;
173
+ top: 20px;
174
+ left: 50%;
175
+ transform: translateX(-50%);
176
+ background: ${colors[type]};
177
+ color: white;
178
+ padding: 10px 20px;
179
+ border-radius: 6px;
180
+ font-family: 'Segoe UI', Arial, sans-serif;
181
+ font-size: 14px;
182
+ z-index: 10001;
183
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
184
+ opacity: 0;
185
+ transition: opacity 0.3s ease;
186
+ `, message);
187
+
188
+ document.body.appendChild(toast);
189
+
190
+ // 淡入淡出效果
191
+ setTimeout(() => toast.style.opacity = '1', 100);
192
+ setTimeout(() => {
193
+ toast.style.opacity = '0';
194
+ setTimeout(() => toast.remove(), 300);
195
+ }, 3000);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 拖拽功能混入
201
+ */
202
+ class DragHandler {
203
+ constructor(element, handle = null) {
204
+ this.element = element;
205
+ this.handle = handle || element;
206
+ this.isDragging = false;
207
+ this.offset = { x: 0, y: 0 };
208
+
209
+ // 绑定事件处理函数,避免内存泄漏
210
+ this.boundMouseDown = this.handleMouseDown.bind(this);
211
+ this.boundMouseMove = this.handleMouseMove.bind(this);
212
+ this.boundMouseUp = this.handleMouseUp.bind(this);
213
+
214
+ this.setupDrag();
215
+ }
216
+
217
+ setupDrag() {
218
+ this.handle.addEventListener('mousedown', this.boundMouseDown);
219
+ }
220
+
221
+ handleMouseDown(e) {
222
+ this.isDragging = true;
223
+ const rect = this.element.getBoundingClientRect();
224
+ this.offset.x = e.clientX - rect.left;
225
+ this.offset.y = e.clientY - rect.top;
226
+
227
+ this.handle.style.cursor = 'grabbing';
228
+ document.addEventListener('mousemove', this.boundMouseMove);
229
+ document.addEventListener('mouseup', this.boundMouseUp);
230
+ e.preventDefault();
231
+ }
232
+
233
+ handleMouseMove(e) {
234
+ if (!this.isDragging) return;
235
+
236
+ const x = e.clientX - this.offset.x;
237
+ const y = e.clientY - this.offset.y;
238
+ const maxX = window.innerWidth - this.element.offsetWidth;
239
+ const maxY = window.innerHeight - this.element.offsetHeight;
240
+
241
+ this.element.style.left = Math.max(0, Math.min(x, maxX)) + 'px';
242
+ this.element.style.top = Math.max(0, Math.min(y, maxY)) + 'px';
243
+ this.element.style.right = 'auto';
244
+ this.element.style.bottom = 'auto';
245
+ this.element.style.transform = 'none';
246
+ }
247
+
248
+ handleMouseUp() {
249
+ this.isDragging = false;
250
+ this.handle.style.cursor = 'move';
251
+ document.removeEventListener('mousemove', this.boundMouseMove);
252
+ document.removeEventListener('mouseup', this.boundMouseUp);
253
+ }
254
+
255
+ // 添加销毁方法,用于清理资源
256
+ destroy() {
257
+ this.handle.removeEventListener('mousedown', this.boundMouseDown);
258
+ document.removeEventListener('mousemove', this.boundMouseMove);
259
+ document.removeEventListener('mouseup', this.boundMouseUp);
260
+ }
261
+ }
262
+
263
+ /**
264
+ * 样式工具类
265
+ */
266
+ class StyleUtils {
267
+ // 默认样式值缓存
268
+ static defaultStyleCache = new Map();
269
+
270
+ /**
271
+ * 获取元素默认样式值(带缓存)
272
+ */
273
+ static getDefaultStyleValue(tagName, property) {
274
+ const cacheKey = `${tagName}-${property}`;
275
+ if (this.defaultStyleCache.has(cacheKey)) {
276
+ return this.defaultStyleCache.get(cacheKey);
277
+ }
278
+
279
+ try {
280
+ const testElement = document.createElement(tagName);
281
+ testElement.style.visibility = 'hidden';
282
+ testElement.style.position = 'absolute';
283
+ testElement.style.top = '-9999px';
284
+ document.body.appendChild(testElement);
285
+
286
+ const defaultComputedStyle = window.getComputedStyle(testElement);
287
+ const defaultValue = defaultComputedStyle[property];
288
+
289
+ document.body.removeChild(testElement);
290
+
291
+ // 缓存结果
292
+ this.defaultStyleCache.set(cacheKey, defaultValue);
293
+ return defaultValue;
294
+ } catch (e) {
295
+ console.warn('获取默认样式值失败:', e);
296
+ return null;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * 为需要单位的值自动添加px
302
+ */
303
+ static addUnitIfNeeded(value, property) {
304
+ if (!value || value.trim() === '') return '';
305
+
306
+ const trimmedValue = value.trim();
307
+ const pixelProperties = [
308
+ 'width', 'height', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
309
+ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'fontSize'
310
+ ];
311
+
312
+ // lineHeight特殊处理
313
+ if (property === 'lineHeight') {
314
+ if (/^[\d.]+$/.test(trimmedValue)) return trimmedValue;
315
+ if (/^[\d.-]+(?:px|em|rem|%|vh|vw|vmin|vmax|cm|mm|in|pt|pc|ex|ch)$/i.test(trimmedValue)) {
316
+ return trimmedValue;
317
+ }
318
+ return trimmedValue;
319
+ }
320
+
321
+ if (!pixelProperties.includes(property)) return trimmedValue;
322
+ if (/^[\d.-]+(?:px|em|rem|%|vh|vw|vmin|vmax|cm|mm|in|pt|pc|ex|ch)$/i.test(trimmedValue)) {
323
+ return trimmedValue;
324
+ }
325
+ if (/^-?[\d.]+$/.test(trimmedValue)) return trimmedValue + 'px';
326
+
327
+ const specialValues = ['auto', 'inherit', 'initial', 'unset', 'none', 'normal'];
328
+ return specialValues.includes(trimmedValue.toLowerCase()) ? trimmedValue : trimmedValue;
329
+ }
330
+
331
+ /**
332
+ * 格式化样式值
333
+ */
334
+ static formatValue(value) {
335
+ if (!value || value === '0px') return '';
336
+ // 保留auto等有意义的值
337
+ if (value === 'auto' || value === 'inherit' || value === 'initial' || value === 'unset') {
338
+ return value;
339
+ }
340
+ return value;
341
+ }
342
+
343
+ /**
344
+ * 格式化背景颜色值 - 专门处理背景颜色的显示
345
+ */
346
+ static formatBackgroundColor(value) {
347
+ if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)' || value === 'initial' || value === 'inherit') {
348
+ return ''; // 返回空字符串,在输入框中显示为空
349
+ }
350
+ return value;
351
+ }
352
+
353
+ /**
354
+ * 获取背景颜色的显示文本
355
+ */
356
+ static getBackgroundColorDisplayText(value) {
357
+ if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)' || value === 'initial' || value === 'inherit') {
358
+ return '无'; // 在界面上显示"无"
359
+ }
360
+ return value;
361
+ }
362
+
363
+ /**
364
+ * 检查是否有有效的背景颜色
365
+ */
366
+ static hasValidBackgroundColor(value) {
367
+ return value && value !== 'transparent' && value !== 'rgba(0, 0, 0, 0)' && value !== 'initial' && value !== 'inherit';
368
+ }
369
+
370
+ /**
371
+ * RGB转十六进制 - 优化背景颜色处理
372
+ */
373
+ static rgbToHex(rgb) {
374
+ // 如果没有有效的背景颜色,返回白色作为默认值供颜色选择器使用
375
+ if (!rgb || rgb === 'transparent' || rgb === 'rgba(0, 0, 0, 0)' || rgb === 'initial' || rgb === 'inherit') {
376
+ return '#ffffff';
377
+ }
378
+ if (rgb.startsWith('#')) return rgb;
379
+
380
+ const result = rgb.match(/\d+/g);
381
+ if (result && result.length >= 3) {
382
+ const r = parseInt(result[0]);
383
+ const g = parseInt(result[1]);
384
+ const b = parseInt(result[2]);
385
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
386
+ }
387
+ return '#ffffff'; // 默认白色
388
+ }
389
+
390
+ /**
391
+ * 获取样式值(优先内联样式)
392
+ */
393
+ static getStyleValue(element, property, computedValue) {
394
+ const inlineValue = element.style[property];
395
+ return inlineValue || computedValue || '';
396
+ }
397
+
398
+ /**
399
+ * 获取显式设置的尺寸值(用于样式编辑器显示)
400
+ * 如果元素明确设置为auto,则返回"auto"而不是计算值
401
+ */
402
+ static getExplicitSizeValue(element, property, computedValue) {
403
+ // 1. 首先检查内联样式
404
+ const inlineValue = element.style[property];
405
+ if (inlineValue) {
406
+ return inlineValue; // 直接返回内联样式值,包括"auto"
407
+ }
408
+
409
+ // 2. 使用浏览器API检查元素的样式规则
410
+ // 创建一个临时元素来对比,看看是否有显式设置
411
+ const tagName = element.tagName.toLowerCase();
412
+
413
+ // 对于特殊元素类型,直接返回计算值
414
+ const mediaElements = ['img', 'video', 'audio', 'canvas', 'svg', 'iframe', 'embed', 'object'];
415
+ const inputElements = ['input', 'button', 'textarea', 'select'];
416
+
417
+ if (mediaElements.includes(tagName) || inputElements.includes(tagName)) {
418
+ return computedValue || '';
419
+ }
420
+
421
+ // 3. 检查getComputedStyle的各个来源
422
+ // 使用getComputedStyle获取样式,然后检查是否有显式设置
423
+ try {
424
+ // 遍历所有样式表,查找匹配的规则
425
+ const sheets = Array.from(document.styleSheets);
426
+ let foundExplicitValue = null;
427
+
428
+ for (let sheet of sheets) {
429
+ try {
430
+ const rules = Array.from(sheet.cssRules || sheet.rules || []);
431
+ for (let rule of rules) {
432
+ if (rule.style && rule.selectorText) {
433
+ // 检查元素是否匹配这个CSS规则
434
+ try {
435
+ if (element.matches && element.matches(rule.selectorText)) {
436
+ const ruleValue = rule.style[property];
437
+ if (ruleValue) {
438
+ foundExplicitValue = ruleValue;
439
+ // 不立即返回,继续查找可能的更具体的规则
440
+ }
441
+ }
442
+ } catch (selectorError) {
443
+ // 无效的选择器语法,跳过
444
+ continue;
445
+ }
446
+ }
447
+ }
448
+ } catch (e) {
449
+ // 跨域样式表或其他错误,跳过
450
+ continue;
451
+ }
452
+ }
453
+
454
+ if (foundExplicitValue) {
455
+ return foundExplicitValue;
456
+ }
457
+ } catch (e) {
458
+ console.warn('CSS规则检测失败:', e);
459
+ }
460
+
461
+ // 4. 如果没有找到显式设置,但计算值不是默认值,可能通过其他方式设置
462
+ // 检查计算值是否明显是被设置的
463
+ const computedStyle = window.getComputedStyle(element);
464
+ const actualComputedValue = computedStyle[property];
465
+
466
+ if (actualComputedValue && actualComputedValue !== 'auto') {
467
+ // 使用缓存的方法获取默认值
468
+ const defaultValue = this.getDefaultStyleValue(tagName, property);
469
+
470
+ if (defaultValue !== null && actualComputedValue !== defaultValue) {
471
+ return actualComputedValue;
472
+ }
473
+ }
474
+
475
+ // 5. 如果都没有找到,返回空字符串表示未设置
476
+ return '';
477
+ }
478
+
479
+ /**
480
+ * 检查元素是否明确设置了某个尺寸属性(通过内联样式或CSS类)
481
+ */
482
+ static hasExplicitSizeProperty(element, property) {
483
+ // 只检查宽度和高度属性
484
+ if (property !== 'width' && property !== 'height') {
485
+ return true; // 其他属性默认允许编辑
486
+ }
487
+
488
+ // 1. 检查内联样式(包括auto)
489
+ if (element.style[property]) {
490
+ return true; // 无论是什么值,只要设置了就允许编辑
491
+ }
492
+
493
+ // 2. 特殊元素类型处理
494
+ const tagName = element.tagName.toLowerCase();
495
+ const mediaElements = ['img', 'video', 'audio', 'canvas', 'svg', 'iframe', 'embed', 'object'];
496
+ const inputElements = ['input', 'button', 'textarea', 'select'];
497
+
498
+ // 媒体元素和表单元素通常有默认尺寸,允许修改
499
+ if (mediaElements.includes(tagName) || inputElements.includes(tagName)) {
500
+ return true;
501
+ }
502
+
503
+ // 3. 检查CSS规则是否设置了该属性
504
+ try {
505
+ const sheets = Array.from(document.styleSheets);
506
+ for (let sheet of sheets) {
507
+ try {
508
+ const rules = Array.from(sheet.cssRules || sheet.rules || []);
509
+ for (let rule of rules) {
510
+ if (rule.style && rule.selectorText) {
511
+ // 检查元素是否匹配这个CSS规则
512
+ if (element.matches && element.matches(rule.selectorText)) {
513
+ const ruleValue = rule.style[property];
514
+ if (ruleValue) { // 任何值都算是显式设置,包括auto
515
+ return true;
516
+ }
517
+ }
518
+ }
519
+ }
520
+ } catch (e) {
521
+ // 跨域样式表或其他错误,跳过
522
+ continue;
523
+ }
524
+ }
525
+ } catch (e) {
526
+ console.warn('CSS规则检测失败:', e);
527
+ }
528
+
529
+ // 4. 最后检查计算值是否与默认值不同
530
+ const computedStyle = window.getComputedStyle(element);
531
+ const computedValue = computedStyle[property];
532
+
533
+ if (computedValue && computedValue !== 'auto') {
534
+ // 使用缓存的方法获取默认值
535
+ const defaultValue = this.getDefaultStyleValue(tagName, property);
536
+
537
+ if (defaultValue !== null && computedValue !== defaultValue) {
538
+ return true;
539
+ }
540
+ }
541
+
542
+ return false;
543
+ }
544
+
545
+ /**
546
+ * 获取尺寸属性的状态文本
547
+ */
548
+ static getSizePropertyStatus(element, property) {
549
+ if (this.hasExplicitSizeProperty(element, property)) {
550
+ return '可编辑';
551
+ } else {
552
+ return '未设置';
553
+ }
554
+ }
555
+ }
556
+
557
+ // 状态变量
558
+ let debugMode = false;
559
+ let currentHighlightedElement = null;
560
+ let highlightOverlay = null;
561
+ let debugOverlay = null;
562
+ let currentStyleEditorElement = null;
563
+
564
+ /**
565
+ * 创建可折叠面板的通用方法
566
+ */
567
+ function createCollapsiblePanel(id, title, content, options = {}) {
568
+ const {
569
+ width = '300px',
570
+ maxWidth = '500px',
571
+ position = { top: '10px', right: '10px' },
572
+ zIndex = 10000
573
+ } = options;
574
+
575
+ const panel = UIHelper.createElement('div', id, 'pug-debug-element', `
576
+ position: fixed;
577
+ top: ${position.top};
578
+ right: ${position.right};
579
+ background: rgba(0, 0, 0, 0.9);
580
+ color: white;
581
+ border-radius: 8px;
582
+ font-family: 'Courier New', monospace;
583
+ font-size: 12px;
584
+ z-index: ${zIndex};
585
+ min-width: ${width};
586
+ max-width: ${maxWidth};
587
+ max-height: 95vh;
588
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
589
+ display: none;
590
+ user-select: none;
591
+ `);
592
+
593
+ const header = UIHelper.createElement('div', `${id}-header`, 'pug-debug-element',
594
+ DEBUG_STYLES.headerStyle);
595
+
596
+ const titleSection = UIHelper.createElement('div', '', '', `
597
+ display: flex;
598
+ align-items: center;
599
+ gap: 8px;
600
+ `, `
601
+ <span style="font-weight: bold; color: #4CAF50;">${title}</span>
602
+ <span style="color: #888; font-size: 10px;margin-right: 10px;">拖拽移动</span>
603
+ `);
604
+
605
+ const buttonSection = UIHelper.createElement('div', '', '', `
606
+ display: flex;
607
+ align-items: center;
608
+ gap: 5px;
609
+ `);
610
+
611
+ const collapseBtn = UIHelper.createButton(`${id}-collapse`, '−', 'neutral');
612
+ const closeBtn = UIHelper.createButton(`${id}-close`, '×', 'danger');
613
+
614
+ buttonSection.appendChild(collapseBtn);
615
+ buttonSection.appendChild(closeBtn);
616
+ header.appendChild(titleSection);
617
+ header.appendChild(buttonSection);
618
+
619
+ const contentDiv = UIHelper.createElement('div', `${id}-content`, 'pug-debug-element',
620
+ DEBUG_STYLES.contentStyle, content);
621
+
622
+ panel.appendChild(header);
623
+ panel.appendChild(contentDiv);
624
+
625
+ // 设置拖拽
626
+ new DragHandler(panel, header);
627
+
628
+ // 折叠功能
629
+ let isCollapsed = false;
630
+ collapseBtn.addEventListener('click', () => {
631
+ isCollapsed = !isCollapsed;
632
+ contentDiv.style.display = isCollapsed ? 'none' : 'block';
633
+ collapseBtn.textContent = isCollapsed ? '+' : '−';
634
+ panel.style.minWidth = isCollapsed ? 'auto' : width;
635
+ });
636
+
637
+ return { panel, contentDiv, closeBtn };
638
+ }
639
+
640
+ // 创建调试信息显示面板
641
+ function createDebugOverlay() {
642
+ console.log("🐛 createDebugOverlay 被调用,已存在的 debugOverlay:", !!debugOverlay);
643
+ if (debugOverlay) return;
644
+
645
+ const { panel, contentDiv, closeBtn } = createCollapsiblePanel(
646
+ 'pug-debug-overlay',
647
+ '🐛 Pug Debug Info',
648
+ ''
649
+ );
650
+
651
+ debugOverlay = panel;
652
+ closeBtn.addEventListener('click', hideDebugInfo);
653
+ document.body.appendChild(debugOverlay);
654
+ }
655
+
656
+ // 创建调试模式切换按钮
657
+ function createDebugToggle() {
658
+ const toggle = UIHelper.createElement('div', 'pug-debug-toggle', 'pug-debug-element', `
659
+ position: fixed;
660
+ bottom: 20px;
661
+ right: 20px;
662
+ width: 50px;
663
+ height: 50px;
664
+ background: #2196F3;
665
+ border-radius: 50%;
666
+ display: flex;
667
+ align-items: center;
668
+ justify-content: center;
669
+ cursor: pointer;
670
+ z-index: 9999;
671
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
672
+ transition: background-color 0.3s;
673
+ `, '🐛');
674
+
675
+ toggle.title = '开启调试模式';
676
+ toggle.addEventListener('click', toggleDebugMode);
677
+ document.body.appendChild(toggle);
678
+ }
679
+
680
+ // 切换调试模式
681
+ function toggleDebugMode() {
682
+ debugMode = !debugMode;
683
+ const toggle = document.getElementById('pug-debug-toggle');
684
+
685
+ if (debugMode) {
686
+ toggle.style.background = '#4CAF50';
687
+ toggle.title = '关闭调试模式 (ESC)';
688
+ document.body.style.cursor = 'crosshair';
689
+ UIHelper.showToast('🐛 调试模式已开启,点击任意元素查看调试信息', 'success');
690
+ } else {
691
+ toggle.style.background = '#2196F3';
692
+ toggle.title = '开启调试模式';
693
+ document.body.style.cursor = '';
694
+ clearHighlight();
695
+ hideDebugInfo();
696
+ UIHelper.showToast('🐛 调试模式已关闭', 'info');
697
+ }
698
+ }
699
+
700
+ // 创建高亮覆盖层
701
+ function createHighlightOverlay() {
702
+ if (highlightOverlay) return;
703
+
704
+ highlightOverlay = UIHelper.createElement('div', 'pug-debug-highlight', 'pug-debug-element', `
705
+ position: absolute;
706
+ pointer-events: none;
707
+ border: 2px solid #ff4444;
708
+ background: rgba(255, 68, 68, 0.1);
709
+ box-shadow: 0 0 10px rgba(255, 68, 68, 0.5);
710
+ z-index: 9998;
711
+ display: none;
712
+ box-sizing: border-box;
713
+ `);
714
+
715
+ document.body.appendChild(highlightOverlay);
716
+ }
717
+
718
+ // 高亮元素
719
+ function highlightElement(element) {
720
+ clearHighlight();
721
+ currentHighlightedElement = element;
722
+
723
+ createHighlightOverlay();
724
+
725
+ // 获取元素的位置和尺寸
726
+ const rect = element.getBoundingClientRect();
727
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
728
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
729
+
730
+ // 设置高亮覆盖层的位置和尺寸
731
+ highlightOverlay.style.display = "block";
732
+ highlightOverlay.style.left = rect.left + scrollLeft + "px";
733
+ highlightOverlay.style.top = rect.top + scrollTop + "px";
734
+ highlightOverlay.style.width = rect.width + "px";
735
+ highlightOverlay.style.height = rect.height + "px";
736
+ }
737
+
738
+ // 清除高亮
739
+ function clearHighlight() {
740
+ if (highlightOverlay) {
741
+ highlightOverlay.style.display = "none";
742
+ }
743
+ currentHighlightedElement = null;
744
+ }
745
+
746
+ // 获取元素自身的纯文本内容(不包括子元素)
747
+ function getElementOwnText(element) {
748
+ return (
749
+ Array.from(element.childNodes)
750
+ .filter((node) => node.nodeType === 3)
751
+ .map((node) => node.textContent.trim())
752
+ .join("") || "无"
753
+ );
754
+ }
755
+
756
+ // 显示调试信息
757
+ function showDebugInfo(element) {
758
+ console.log("🐛 showDebugInfo 被调用,element:", element);
759
+ createDebugOverlay();
760
+
761
+ const debugFile = element.getAttribute("data-debug-file");
762
+ const debugLine = element.getAttribute("data-debug-line");
763
+ const debugTag = element.tagName.toLowerCase();
764
+ const ownText = getElementOwnText(element);
765
+ const debugEditable = element.getAttribute("data-debug-editable");
766
+
767
+ console.log("🐛 调试信息:", { debugFile, debugLine, debugTag, ownText, debugEditable });
768
+
769
+ let content = "";
770
+
771
+ if (debugFile || debugLine) {
772
+ // 判断文本内容是否可编辑
773
+ const isEditable = debugEditable !== null;
774
+ const textContentHtml = isEditable
775
+ ? `<span id="editable-text" style="color: #87CEEB; cursor: pointer; text-decoration: underline;" title="单击编辑">${ownText}</span> <span style="color: #4CAF50; font-size: 10px;">✏️可编辑</span>`
776
+ : `<span style="color: #87CEEB;">${ownText}</span>`;
777
+
778
+ content = `
779
+ <div style="margin-bottom: 8px;">
780
+ <span style="color: #FFD700;">文件:</span>
781
+ <span style="color: #87CEEB;">${debugFile || "未知"}</span>
782
+ </div>
783
+ <div style="margin-bottom: 8px;">
784
+ <span style="color: #FFD700;">行号:</span>
785
+ <span style="color: #87CEEB;">${debugLine || "未知"}</span>
786
+ </div>
787
+ <div style="margin-bottom: 8px;">
788
+ <span style="color: #FFD700;">标签:</span>
789
+ <span style="color: #87CEEB;">${debugTag}</span>
790
+ </div>
791
+ <div style="margin-bottom: 8px;">
792
+ <span style="color: #FFD700;">ID:</span>
793
+ <span style="color: #87CEEB;">${element.id || "无"}</span>
794
+ </div>
795
+ <div style="margin-bottom: 8px;">
796
+ <span style="color: #FFD700;">类名:</span>
797
+ <span style="color: #87CEEB;">${element.className || "无"}</span>
798
+ </div>
799
+ <div style="margin-bottom: 8px;">
800
+ <span style="color: #FFD700;">文本内容:</span>
801
+ ${textContentHtml}
802
+ </div>
803
+ <div style="margin-bottom: 10px;">
804
+ <button id="edit-style-btn" class="pug-debug-element" style="
805
+ background: #FF9800;
806
+ color: white;
807
+ border: none;
808
+ border-radius: 4px;
809
+ padding: 6px 12px;
810
+ cursor: pointer;
811
+ font-size: 12px;
812
+ margin-right: 8px;
813
+ ">🎨 编辑样式</button>
814
+ </div>
815
+ <hr style="border: 1px solid #444; margin: 10px 0;">
816
+ <div style="font-size: 11px; color: #AAA;">
817
+ 💡 点击其他元素查看调试信息,点击切换按钮关闭调试模式
818
+ ${isEditable ? "<br/>✏️ 单击文本内容可进行编辑" : ""}
819
+ <br/>🎨 点击"编辑样式"按钮可修改元素样式
820
+ </div>
821
+ `;
822
+
823
+ // 发送调试信息到服务端
824
+ sendDebugInfoToServer({
825
+ file: debugFile,
826
+ line: debugLine,
827
+ tag: debugTag,
828
+ id: element.id,
829
+ className: element.className,
830
+ textContent: getElementOwnText(element),
831
+ editableKey: debugEditable,
832
+ });
833
+ } else {
834
+ content = `
835
+ <div style="color: #FFA500;">
836
+ ⚠️ 此元素没有调试信息
837
+ </div>
838
+ <div style="margin-top: 8px;">
839
+ <span style="color: #FFD700;">标签:</span>
840
+ <span style="color: #87CEEB;">${debugTag}</span>
841
+ </div>
842
+ <div style="margin-top: 8px;">
843
+ <span style="color: #FFD700;">ID:</span>
844
+ <span style="color: #87CEEB;">${element.id || "无"}</span>
845
+ </div>
846
+ <div style="margin-top: 8px;">
847
+ <span style="color: #FFD700;">类名:</span>
848
+ <span style="color: #87CEEB;">${element.className || "无"}</span>
849
+ </div>
850
+ <div style="margin-top: 8px;">
851
+ <span style="color: #FFD700;">文本内容:</span>
852
+ <span style="color: #87CEEB;">${ownText}</span>
853
+ </div>
854
+ <div style="margin: 10px 0;">
855
+ <button id="edit-style-btn" class="pug-debug-element" style="
856
+ background: #FF9800;
857
+ color: white;
858
+ border: none;
859
+ border-radius: 4px;
860
+ padding: 6px 12px;
861
+ cursor: pointer;
862
+ font-size: 12px;
863
+ ">🎨 编辑样式</button>
864
+ </div>
865
+ <hr style="border: 1px solid #444; margin: 10px 0;">
866
+ <div style="margin-top: 8px; font-size: 11px; color: #AAA;">
867
+ 💡 可能是动态生成的元素或非调试模板中的元素
868
+ <br/>🎨 点击"编辑样式"按钮可修改元素样式
869
+ </div>
870
+ `;
871
+ }
872
+
873
+ document.getElementById("pug-debug-overlay-content").innerHTML = content;
874
+ debugOverlay.style.display = "block";
875
+ console.log("🐛 调试面板已显示,debugOverlay:", debugOverlay);
876
+
877
+ // 如果有可编辑文本,添加单击事件
878
+ const editableText = document.getElementById("editable-text");
879
+ if (editableText) {
880
+ editableText.addEventListener("click", function (e) {
881
+ e.stopPropagation();
882
+ startTextEdit(
883
+ element,
884
+ editableText,
885
+ ownText,
886
+ debugEditable,
887
+ debugFile,
888
+ debugLine
889
+ );
890
+ });
891
+ }
892
+
893
+ // 添加样式编辑按钮事件
894
+ const editStyleBtn = document.getElementById("edit-style-btn");
895
+ if (editStyleBtn) {
896
+ editStyleBtn.addEventListener("click", function (e) {
897
+ e.stopPropagation();
898
+ openStyleEditor(element);
899
+ });
900
+ }
901
+
902
+ // 如果样式编辑器已经打开,自动更新其内容
903
+ if (document.getElementById("pug-style-editor")) {
904
+ updateStyleEditor(element);
905
+ }
906
+ }
907
+
908
+ // 隐藏调试信息
909
+ function hideDebugInfo() {
910
+ if (debugOverlay) {
911
+ debugOverlay.style.display = "none";
912
+ }
913
+ }
914
+
915
+ // 开始文本编辑
916
+ function startTextEdit(
917
+ element,
918
+ textElement,
919
+ originalText,
920
+ editableKey,
921
+ debugFile,
922
+ debugLine
923
+ ) {
924
+ // 创建输入框
925
+ const input = UIHelper.createElement('input', '', '', `
926
+ background: #333;
927
+ color: #87CEEB;
928
+ border: 1px solid #4CAF50;
929
+ border-radius: 3px;
930
+ padding: 2px 5px;
931
+ font-family: 'Courier New', monospace;
932
+ font-size: 12px;
933
+ width: 200px;
934
+ `);
935
+ input.type = 'text';
936
+ input.value = originalText;
937
+
938
+ // 创建按钮
939
+ const saveBtn = UIHelper.createButton('', '保存', 'primary');
940
+ saveBtn.style.marginLeft = '5px';
941
+ saveBtn.style.fontSize = '11px';
942
+ saveBtn.style.padding = '2px 8px';
943
+
944
+ const cancelBtn = UIHelper.createButton('', '取消', 'danger');
945
+ cancelBtn.style.marginLeft = '5px';
946
+ cancelBtn.style.fontSize = '11px';
947
+ cancelBtn.style.padding = '2px 8px';
948
+
949
+ // 创建编辑容器
950
+ const editContainer = UIHelper.createElement('div', '', 'pug-debug-element', `
951
+ display: flex;
952
+ align-items: center;
953
+ margin-top: 5px;
954
+ `);
955
+
956
+ editContainer.appendChild(input);
957
+ editContainer.appendChild(saveBtn);
958
+ editContainer.appendChild(cancelBtn);
959
+
960
+ // 替换原始文本元素
961
+ const parentDiv = textElement.parentElement;
962
+
963
+ // 隐藏原始文本,显示编辑界面
964
+ textElement.style.display = "none";
965
+ parentDiv.appendChild(editContainer);
966
+
967
+ // 聚焦输入框并选中文本
968
+ input.focus();
969
+ input.select();
970
+
971
+ // 保存功能
972
+ function saveEdit() {
973
+ const newText = input.value.trim();
974
+ if (newText !== originalText) {
975
+ // 发送编辑信息到服务端
976
+ sendTextEditToServer({
977
+ file: debugFile,
978
+ line: debugLine,
979
+ editableKey: editableKey,
980
+ originalText: originalText,
981
+ newText: newText,
982
+ element: {
983
+ tag: element.tagName.toLowerCase(),
984
+ id: element.id,
985
+ className: element.className,
986
+ },
987
+ });
988
+
989
+ // 更新页面上的文本
990
+ element.childNodes.forEach((node) => {
991
+ if (node.nodeType === 3 && node.textContent.trim() === originalText) {
992
+ node.textContent = newText;
993
+ }
994
+ });
995
+
996
+ UIHelper.showToast(`文本已更新: "${originalText}" → "${newText}"`, "success");
997
+ }
998
+
999
+ // 恢复原始显示
1000
+ editContainer.remove();
1001
+ textElement.style.display = "";
1002
+ textElement.textContent = newText;
1003
+ }
1004
+
1005
+ // 取消功能
1006
+ function cancelEdit() {
1007
+ editContainer.remove();
1008
+ textElement.style.display = "";
1009
+ }
1010
+
1011
+ // 绑定事件
1012
+ saveBtn.addEventListener("click", saveEdit);
1013
+ cancelBtn.addEventListener("click", cancelEdit);
1014
+
1015
+ // 回车保存,ESC取消
1016
+ input.addEventListener("keydown", function (e) {
1017
+ if (e.key === "Enter") {
1018
+ e.preventDefault();
1019
+ saveEdit();
1020
+ }
1021
+ });
1022
+
1023
+ // 点击其他地方取消编辑
1024
+ document.addEventListener("click", function outsideClick(e) {
1025
+ if (!editContainer.contains(e.target)) {
1026
+ document.removeEventListener("click", outsideClick);
1027
+ cancelEdit();
1028
+ }
1029
+ });
1030
+ }
1031
+
1032
+ // 打开样式编辑器
1033
+ function openStyleEditor(element) {
1034
+ // 如果样式编辑器已经打开并且是同一个元素,直接返回
1035
+ if (
1036
+ currentStyleEditorElement === element &&
1037
+ document.getElementById("pug-style-editor")
1038
+ ) {
1039
+ return;
1040
+ }
1041
+
1042
+ // 设置当前编辑的元素
1043
+ currentStyleEditorElement = element;
1044
+
1045
+ // 移除现有的样式编辑器
1046
+ const existingEditor = document.getElementById("pug-style-editor");
1047
+ if (existingEditor) {
1048
+ existingEditor.remove();
1049
+ }
1050
+
1051
+ createStyleEditor(element);
1052
+ }
1053
+
1054
+ // 创建样式编辑器
1055
+ function createStyleEditor(element) {
1056
+ // 获取元素当前的计算样式
1057
+ const computedStyle = window.getComputedStyle(element);
1058
+ // 获取元素的实际样式值(优先使用内联样式,然后是计算样式)
1059
+ function getStyleValue(property, computedValue) {
1060
+ return StyleUtils.getStyleValue(element, property, computedValue);
1061
+ }
1062
+
1063
+ // 创建样式编辑器内容
1064
+ const content = createStyleEditorContent(element, computedStyle, getStyleValue);
1065
+
1066
+ const { panel, contentDiv, closeBtn } = createCollapsiblePanel(
1067
+ 'pug-style-editor',
1068
+ '🎨 样式编辑器',
1069
+ content,
1070
+ {
1071
+ width: '450px',
1072
+ position: { top: '50%', left: '50%' },
1073
+ zIndex: 10002
1074
+ }
1075
+ );
1076
+
1077
+ // 设置居中定位
1078
+ panel.style.transform = 'translate(-50%, -50%)';
1079
+ panel.style.right = 'auto';
1080
+ panel.style.top = '50%';
1081
+ panel.style.left = '50%';
1082
+
1083
+ closeBtn.addEventListener('click', () => {
1084
+ currentStyleEditorElement = null;
1085
+ panel.remove();
1086
+ });
1087
+
1088
+ document.body.appendChild(panel);
1089
+ panel.style.display = 'block';
1090
+
1091
+ // 设置事件处理
1092
+ setupStyleEditorEvents(element, panel);
1093
+ }
1094
+
1095
+ // 创建样式编辑器内容
1096
+ function createStyleEditorContent(element, computedStyle, getStyleValue) {
1097
+ // 元素类型分类系统
1098
+ const tagName = element.tagName.toLowerCase();
1099
+
1100
+ // 定义元素类型和对应的样式组
1101
+ const elementTypes = {
1102
+ // 媒体元素 - 不支持文字样式
1103
+ media: ['img', 'video', 'audio', 'canvas', 'svg', 'iframe', 'embed', 'object'],
1104
+ // 表单元素 - 部分文字样式
1105
+ form: ['input', 'button', 'textarea', 'select', 'option', 'label'],
1106
+ // 表格元素
1107
+ table: ['table', 'tr', 'td', 'th', 'thead', 'tbody', 'tfoot'],
1108
+ // 列表元素
1109
+ list: ['ul', 'ol', 'li'],
1110
+ // 文本元素 - 支持所有文字样式
1111
+ text: ['p', 'span', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong', 'em', 'b', 'i', 'u', 'small', 'mark', 'del', 'ins', 'sub', 'sup'],
1112
+ // 容器元素 - 根据内容决定
1113
+ container: ['div', 'section', 'article', 'aside', 'nav', 'header', 'footer', 'main']
1114
+ };
1115
+
1116
+ // 判断元素类型
1117
+ function getElementType(tag) {
1118
+ for (const [type, tags] of Object.entries(elementTypes)) {
1119
+ if (tags.includes(tag)) return type;
1120
+ }
1121
+ return 'container'; // 默认为容器类型
1122
+ }
1123
+
1124
+ // 检查元素是否支持文字样式
1125
+ function supportsTextStyles(tag) {
1126
+ const type = getElementType(tag);
1127
+ // 媒体元素不支持文字样式
1128
+ if (type === 'media') return false;
1129
+ // 表单元素中的input根据type决定
1130
+ if (tag === 'input') {
1131
+ const inputType = element.type?.toLowerCase() || 'text';
1132
+ // 这些input类型不支持文字样式
1133
+ const noTextTypes = ['checkbox', 'radio', 'range', 'file', 'color', 'date', 'datetime-local', 'month', 'time', 'week'];
1134
+ return !noTextTypes.includes(inputType);
1135
+ }
1136
+ return true;
1137
+ }
1138
+
1139
+ // 检查元素是否支持内边距(某些元素默认不支持)
1140
+ function supportsPadding(tag) {
1141
+ const type = getElementType(tag);
1142
+ // img等替换元素通常不建议使用padding
1143
+ const noPaddingTags = ['img', 'br', 'hr'];
1144
+ return !noPaddingTags.includes(tag);
1145
+ }
1146
+
1147
+ // 检查元素是否为自闭合标签或不包含文本内容的元素
1148
+ function isVoidElement(tag) {
1149
+ const voidElements = ['img', 'br', 'hr', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr'];
1150
+ return voidElements.includes(tag);
1151
+ }
1152
+
1153
+ const elementType = getElementType(tagName);
1154
+ const hasTextSupport = supportsTextStyles(tagName);
1155
+ const hasPaddingSupport = supportsPadding(tagName);
1156
+ const isVoid = isVoidElement(tagName);
1157
+
1158
+ // 创建输入框的辅助函数
1159
+ function createInputHTML(id, label, value, placeholder) {
1160
+ return `
1161
+ <div>
1162
+ <label style="${DEBUG_STYLES.label}">${label}:</label>
1163
+ <input type="text" id="${id}"
1164
+ value="${StyleUtils.formatValue(value)}"
1165
+ placeholder="${placeholder}"
1166
+ autocomplete="off" spellcheck="false"
1167
+ style="${DEBUG_STYLES.input}" />
1168
+ </div>
1169
+ `;
1170
+ }
1171
+
1172
+ // 创建尺寸输入框的辅助函数(支持禁用状态)
1173
+ function createSizeInputHTML(id, label, value, placeholder, element, property) {
1174
+ const hasExplicitSize = StyleUtils.hasExplicitSizeProperty(element, property);
1175
+ const status = StyleUtils.getSizePropertyStatus(element, property);
1176
+ const isDisabled = !hasExplicitSize;
1177
+
1178
+ // 使用新的方法获取显式设置的值(保留auto等特殊值)
1179
+ const explicitValue = StyleUtils.getExplicitSizeValue(element, property, value);
1180
+ const displayValue = hasExplicitSize ? StyleUtils.formatValue(explicitValue) : '';
1181
+
1182
+ return `
1183
+ <div style="position: relative;">
1184
+ <label style="${DEBUG_STYLES.label}">${label}:</label>
1185
+ <div style="display: flex; align-items: center; gap: 4px;">
1186
+ <input type="text" id="${id}"
1187
+ value="${displayValue}"
1188
+ placeholder="${hasExplicitSize ? (explicitValue || placeholder) : '元素未设置此属性'}"
1189
+ autocomplete="off" spellcheck="false"
1190
+ ${isDisabled ? 'disabled' : ''}
1191
+ style="${DEBUG_STYLES.input}${isDisabled ? '; opacity: 0.5; cursor: not-allowed;' : ''}" />
1192
+ <span style="
1193
+ color: ${hasExplicitSize ? '#4CAF50' : '#FFA500'};
1194
+ font-size: 9px;
1195
+ min-width: 36px;
1196
+ text-align: center;
1197
+ background: ${hasExplicitSize ? 'rgba(76, 175, 80, 0.1)' : 'rgba(255, 165, 0, 0.1)'};
1198
+ border-radius: 3px;
1199
+ padding: 2px 3px;
1200
+ ">${status}</span>
1201
+ </div>
1202
+ ${isDisabled ? `<div style="color: #888; font-size: 9px; margin-top: 2px;">💡 在CSS中设置${label.toLowerCase()}后可编辑</div>` : ''}
1203
+ </div>
1204
+ `;
1205
+ }
1206
+
1207
+ // 创建选择框的辅助函数
1208
+ function createSelectHTML(id, label, options, currentValue) {
1209
+ const optionsHTML = options.map(({ value, text, selected }) =>
1210
+ `<option value="${value}" ${selected || value === currentValue ? 'selected' : ''}>${text}</option>`
1211
+ ).join('');
1212
+
1213
+ return `
1214
+ <div>
1215
+ <label style="${DEBUG_STYLES.label}">${label}:</label>
1216
+ <select id="${id}" style="
1217
+ width: 100%;
1218
+ background: #333;
1219
+ color: white;
1220
+ border: 1px solid #555;
1221
+ border-radius: 3px;
1222
+ padding: 5px;
1223
+ font-size: 11px;
1224
+ box-sizing: border-box;
1225
+ ">
1226
+ ${optionsHTML}
1227
+ </select>
1228
+ </div>
1229
+ `;
1230
+ }
1231
+
1232
+ // 基础样式部分
1233
+ let content = `
1234
+ <style>
1235
+ #pug-style-editor input[type="text"] {
1236
+ background: #333 !important;
1237
+ color: white !important;
1238
+ -webkit-appearance: none !important;
1239
+ -webkit-box-shadow: inset 0 0 0 1000px #333 !important;
1240
+ -webkit-text-fill-color: white !important;
1241
+ }
1242
+ #pug-style-editor input[type="text"]:focus,
1243
+ #pug-style-editor input[type="text"]:hover {
1244
+ background: #333 !important;
1245
+ color: white !important;
1246
+ -webkit-box-shadow: inset 0 0 0 1000px #333 !important;
1247
+ -webkit-text-fill-color: white !important;
1248
+ }
1249
+ #pug-style-editor input[type="text"]::-webkit-input-placeholder {
1250
+ color: #888 !important;
1251
+ }
1252
+ </style>
1253
+
1254
+ <!-- 元素信息 -->
1255
+ <div style="margin-bottom: 12px; padding: 8px; background: rgba(76, 175, 80, 0.1); border-radius: 4px;">
1256
+ <div style="color: #4CAF50; font-size: 11px; margin-bottom: 4px;">
1257
+ 📋 元素信息: &lt;${tagName}&gt; (${elementType === 'media' ? '媒体元素' :
1258
+ elementType === 'form' ? '表单元素' :
1259
+ elementType === 'text' ? '文本元素' :
1260
+ elementType === 'table' ? '表格元素' :
1261
+ elementType === 'list' ? '列表元素' : '容器元素'})
1262
+ </div>
1263
+ ${!hasTextSupport ? '<div style="color: #FFA500; font-size: 10px;">⚠️ 此元素类型不支持文字样式</div>' : ''}
1264
+ ${isVoid ? '<div style="color: #87CEEB; font-size: 10px;">ℹ️ 自闭合标签,无文本内容</div>' : ''}
1265
+ </div>
1266
+
1267
+ <!-- 尺寸样式 -->
1268
+ <div style="margin-bottom: 12px;">
1269
+ <h4 style="color: #4CAF50; margin: 0 0 6px 0; font-size: 13px;">📐 尺寸</h4>
1270
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
1271
+ ${createSizeInputHTML('style-width', '宽度', getStyleValue('width', computedStyle.width), computedStyle.width, element, 'width')}
1272
+ ${createSizeInputHTML('style-height', '高度', getStyleValue('height', computedStyle.height), computedStyle.height, element, 'height')}
1273
+ </div>
1274
+ </div>
1275
+
1276
+ <!-- 外边距样式 -->
1277
+ <div style="margin-bottom: 12px;">
1278
+ <h4 style="color: #4CAF50; margin: 0 0 6px 0; font-size: 13px;">📏 外边距</h4>
1279
+ <div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px;">
1280
+ ${createInputHTML('margin-top', '上', getStyleValue('marginTop', computedStyle.marginTop), computedStyle.marginTop)}
1281
+ ${createInputHTML('margin-right', '右', getStyleValue('marginRight', computedStyle.marginRight), computedStyle.marginRight)}
1282
+ ${createInputHTML('margin-bottom', '下', getStyleValue('marginBottom', computedStyle.marginBottom), computedStyle.marginBottom)}
1283
+ ${createInputHTML('margin-left', '左', getStyleValue('marginLeft', computedStyle.marginLeft), computedStyle.marginLeft)}
1284
+ </div>
1285
+ </div>`;
1286
+
1287
+ // 内边距样式 - 根据元素类型决定是否显示
1288
+ if (hasPaddingSupport) {
1289
+ content += `
1290
+ <div style="margin-bottom: 12px;">
1291
+ <h4 style="color: #4CAF50; margin: 0 0 6px 0; font-size: 13px;">📦 内边距</h4>
1292
+ <div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px;">
1293
+ ${createInputHTML('padding-top', '上', getStyleValue('paddingTop', computedStyle.paddingTop), computedStyle.paddingTop)}
1294
+ ${createInputHTML('padding-right', '右', getStyleValue('paddingRight', computedStyle.paddingRight), computedStyle.paddingRight)}
1295
+ ${createInputHTML('padding-bottom', '下', getStyleValue('paddingBottom', computedStyle.paddingBottom), computedStyle.paddingBottom)}
1296
+ ${createInputHTML('padding-left', '左', getStyleValue('paddingLeft', computedStyle.paddingLeft), computedStyle.paddingLeft)}
1297
+ </div>
1298
+ </div>`;
1299
+ }
1300
+
1301
+ // 字体样式 - 只有支持文字样式的元素才显示
1302
+ if (hasTextSupport) {
1303
+ content += `
1304
+ <div style="margin-bottom: 12px;">
1305
+ <h4 style="color: #4CAF50; margin: 0 0 6px 0; font-size: 13px;">🔤 字体</h4>
1306
+ <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 8px;">
1307
+ ${createInputHTML('font-size', '大小', getStyleValue('fontSize', computedStyle.fontSize), computedStyle.fontSize)}
1308
+ ${createInputHTML('line-height', '行高', getStyleValue('lineHeight', computedStyle.lineHeight), computedStyle.lineHeight)}
1309
+ ${createSelectHTML('font-weight', '粗细', [
1310
+ { value: '', text: `默认 (${computedStyle.fontWeight})` },
1311
+ { value: 'normal', text: 'normal (400)' },
1312
+ { value: 'bold', text: 'bold (700)' },
1313
+ { value: '100', text: '100' },
1314
+ { value: '200', text: '200' },
1315
+ { value: '300', text: '300' },
1316
+ { value: '500', text: '500' },
1317
+ { value: '600', text: '600' },
1318
+ { value: '800', text: '800' },
1319
+ { value: '900', text: '900' }
1320
+ ], getStyleValue('fontWeight', computedStyle.fontWeight))}
1321
+ </div>
1322
+ <div style="display: grid; grid-template-columns: 1fr; gap: 8px;">
1323
+ ${createSelectHTML('text-align', '对齐', [
1324
+ { value: '', text: `默认 (${computedStyle.textAlign})` },
1325
+ { value: 'left', text: '← 左对齐' },
1326
+ { value: 'center', text: '⊙ 居中对齐' },
1327
+ { value: 'right', text: '→ 右对齐' },
1328
+ { value: 'justify', text: '⊞ 两端对齐' },
1329
+ { value: 'start', text: 'start' },
1330
+ { value: 'end', text: 'end' }
1331
+ ], getStyleValue('textAlign', computedStyle.textAlign))}
1332
+ </div>
1333
+ </div>`;
1334
+ }
1335
+
1336
+ // 颜色样式 - 所有元素都支持背景色,但文字颜色只对支持文字的元素显示
1337
+ content += `
1338
+ <div style="margin-bottom: 15px;">
1339
+ <h4 style="color: #4CAF50; margin: 0 0 6px 0; font-size: 13px;">🎨 颜色</h4>
1340
+ <div style="display: grid; grid-template-columns: ${hasTextSupport ? '1fr 1fr' : '1fr'}; gap: 10px;">`;
1341
+
1342
+ if (hasTextSupport) {
1343
+ content += `
1344
+ <div>
1345
+ <label style="${DEBUG_STYLES.label}">文字颜色:</label>
1346
+ <div style="display: flex; gap: 6px;">
1347
+ <input type="color" id="color-picker" value="${StyleUtils.rgbToHex(getStyleValue('color', computedStyle.color))}"
1348
+ style="width: 32px; height: 28px; border: none; background: none; cursor: pointer; padding: 0;" />
1349
+ <input type="text" id="color-text"
1350
+ value="${StyleUtils.formatValue(getStyleValue('color', computedStyle.color))}"
1351
+ placeholder="${computedStyle.color}"
1352
+ autocomplete="off" spellcheck="false"
1353
+ style="flex: 1; ${DEBUG_STYLES.input}" />
1354
+ </div>
1355
+ </div>`;
1356
+ }
1357
+
1358
+ content += `
1359
+ <div>
1360
+ <label style="${DEBUG_STYLES.label}">背景颜色:</label>
1361
+ <div style="display: flex; gap: 6px; align-items: center;">
1362
+ <input type="color" id="bg-color-picker" value="${StyleUtils.rgbToHex(getStyleValue('backgroundColor', computedStyle.backgroundColor))}"
1363
+ style="width: 32px; height: 28px; border: none; background: none; cursor: pointer; padding: 0;" />
1364
+ <input type="text" id="bg-color-text"
1365
+ value="${StyleUtils.formatBackgroundColor(getStyleValue('backgroundColor', computedStyle.backgroundColor))}"
1366
+ placeholder="输入颜色值 (如: #ff0000, red)"
1367
+ autocomplete="off" spellcheck="false"
1368
+ style="flex: 1; ${DEBUG_STYLES.input}" />
1369
+ <span id="bg-color-status" style="
1370
+ color: #87CEEB;
1371
+ font-size: 10px;
1372
+ min-width: 24px;
1373
+ text-align: center;
1374
+ background: rgba(135, 206, 235, 0.1);
1375
+ border-radius: 3px;
1376
+ padding: 2px 4px;
1377
+ ">${StyleUtils.getBackgroundColorDisplayText(getStyleValue('backgroundColor', computedStyle.backgroundColor))}</span>
1378
+ </div>
1379
+ </div>
1380
+ </div>
1381
+ </div>
1382
+
1383
+ <div style="display: flex; gap: 8px; justify-content: flex-end;">
1384
+ <button id="apply-styles" class="pug-debug-element" style="${DEBUG_STYLES.button.base}${DEBUG_STYLES.button.primary}">保存到文件</button>
1385
+ <button id="reset-styles" class="pug-debug-element" style="${DEBUG_STYLES.button.base}background: #FF5722; color: white;">重置样式</button>
1386
+ <button id="cancel-styles" class="pug-debug-element" style="${DEBUG_STYLES.button.base}${DEBUG_STYLES.button.neutral}">取消</button>
1387
+ </div>
1388
+ `;
1389
+
1390
+ return content;
1391
+ }
1392
+
1393
+ /**
1394
+ * 样式编辑跟踪器
1395
+ */
1396
+ class StyleEditTracker {
1397
+ constructor() {
1398
+ this.originalValues = new Map(); // 存储原始值
1399
+ this.editedProperties = new Set(); // 跟踪被编辑的属性
1400
+ this.isInitialized = false;
1401
+ }
1402
+
1403
+ /**
1404
+ * 初始化跟踪器,记录所有输入框的初始值
1405
+ */
1406
+ initialize(element, allStyleInputs) {
1407
+ this.originalValues.clear();
1408
+ this.editedProperties.clear();
1409
+
1410
+ Object.keys(allStyleInputs).forEach((property) => {
1411
+ const elementId = allStyleInputs[property];
1412
+ const inputElement = document.getElementById(elementId);
1413
+ if (inputElement && !inputElement.disabled) {
1414
+ this.originalValues.set(property, inputElement.value.trim());
1415
+ }
1416
+ });
1417
+
1418
+ this.isInitialized = true;
1419
+ console.log('🎨 样式跟踪器已初始化,原始值:', Object.fromEntries(this.originalValues));
1420
+ }
1421
+
1422
+ /**
1423
+ * 标记属性为已编辑
1424
+ */
1425
+ markAsEdited(property) {
1426
+ if (this.isInitialized) {
1427
+ this.editedProperties.add(property);
1428
+ console.log('🎨 标记属性为已编辑:', property, '当前已编辑属性:', Array.from(this.editedProperties));
1429
+ }
1430
+ }
1431
+
1432
+ /**
1433
+ * 检查属性是否被编辑
1434
+ */
1435
+ isEdited(property) {
1436
+ return this.editedProperties.has(property);
1437
+ }
1438
+
1439
+ /**
1440
+ * 获取属性的原始值
1441
+ */
1442
+ getOriginalValue(property) {
1443
+ return this.originalValues.get(property) || '';
1444
+ }
1445
+
1446
+ /**
1447
+ * 检查值是否真正发生了变化
1448
+ */
1449
+ hasValueChanged(property, currentValue) {
1450
+ const originalValue = this.getOriginalValue(property);
1451
+ const trimmedCurrentValue = (currentValue || '').trim();
1452
+ const trimmedOriginalValue = (originalValue || '').trim();
1453
+
1454
+ // 标准化比较:去除空格,处理空字符串
1455
+ const normalizedCurrent = trimmedCurrentValue === '' ? '' : trimmedCurrentValue;
1456
+ const normalizedOriginal = trimmedOriginalValue === '' ? '' : trimmedOriginalValue;
1457
+
1458
+ return normalizedCurrent !== normalizedOriginal;
1459
+ }
1460
+
1461
+ /**
1462
+ * 获取所有被编辑且值发生变化的属性
1463
+ */
1464
+ getEditedStyles(allStyleInputs) {
1465
+ const editedStyles = {};
1466
+
1467
+ this.editedProperties.forEach((property) => {
1468
+ const elementId = allStyleInputs[property];
1469
+ const inputElement = document.getElementById(elementId);
1470
+
1471
+ if (inputElement && !inputElement.disabled) {
1472
+ const currentValue = inputElement.value.trim();
1473
+
1474
+ // 只有当值真正发生变化时才包含在结果中
1475
+ if (this.hasValueChanged(property, currentValue)) {
1476
+ editedStyles[property] = currentValue;
1477
+ }
1478
+ }
1479
+ });
1480
+
1481
+ console.log('🎨 获取编辑后的样式:', editedStyles);
1482
+ return editedStyles;
1483
+ }
1484
+
1485
+ /**
1486
+ * 重置跟踪器
1487
+ */
1488
+ reset() {
1489
+ this.originalValues.clear();
1490
+ this.editedProperties.clear();
1491
+ this.isInitialized = false;
1492
+ }
1493
+ }
1494
+
1495
+ // 创建全局样式跟踪器实例
1496
+ const styleEditTracker = new StyleEditTracker();
1497
+
1498
+ // 设置样式编辑器事件
1499
+ function setupStyleEditorEvents(element, styleEditor) {
1500
+ // 定义所有可能的样式输入元素映射
1501
+ const allStyleInputs = {
1502
+ width: "style-width",
1503
+ height: "style-height",
1504
+ marginTop: "margin-top",
1505
+ marginRight: "margin-right",
1506
+ marginBottom: "margin-bottom",
1507
+ marginLeft: "margin-left",
1508
+ paddingTop: "padding-top",
1509
+ paddingRight: "padding-right",
1510
+ paddingBottom: "padding-bottom",
1511
+ paddingLeft: "padding-left",
1512
+ fontSize: "font-size",
1513
+ lineHeight: "line-height",
1514
+ fontWeight: "font-weight",
1515
+ textAlign: "text-align",
1516
+ color: "color-text",
1517
+ backgroundColor: "bg-color-text",
1518
+ };
1519
+
1520
+ // 初始化样式跟踪器
1521
+ styleEditTracker.initialize(element, allStyleInputs);
1522
+
1523
+ // 获取所有存在的像素相关输入框
1524
+ const allPixelInputIds = [
1525
+ "style-width", "style-height", "margin-top", "margin-right",
1526
+ "margin-bottom", "margin-left", "padding-top", "padding-right",
1527
+ "padding-bottom", "padding-left", "font-size"
1528
+ ];
1529
+
1530
+ // 只为实际存在的输入框添加事件
1531
+ const existingPixelInputIds = allPixelInputIds.filter(id => document.getElementById(id));
1532
+
1533
+ // 实时预览函数
1534
+ function applyStyleRealtime(property, value, inputElement) {
1535
+ try {
1536
+ if (inputElement.disabled) return; // 禁用的输入框不应用样式
1537
+
1538
+ const trimmedValue = (value || '').trim();
1539
+
1540
+ if (trimmedValue) {
1541
+ // 处理值并添加单位
1542
+ const processedValue = StyleUtils.addUnitIfNeeded(trimmedValue, property);
1543
+ element.style[property] = processedValue;
1544
+
1545
+ // 如果值被自动添加了单位,更新输入框显示(在失焦时处理)
1546
+ if (processedValue !== trimmedValue && processedValue.endsWith("px")) {
1547
+ // 不立即更新输入框,避免干扰用户输入
1548
+ }
1549
+ } else {
1550
+ // 清空样式
1551
+ element.style[property] = "";
1552
+ }
1553
+
1554
+ // 特殊处理背景颜色状态显示
1555
+ if (property === 'backgroundColor') {
1556
+ const bgColorStatus = document.getElementById("bg-color-status");
1557
+ if (bgColorStatus) {
1558
+ bgColorStatus.textContent = StyleUtils.getBackgroundColorDisplayText(trimmedValue);
1559
+ }
1560
+ }
1561
+ } catch (error) {
1562
+ console.warn('实时样式应用失败:', property, value, error);
1563
+ }
1564
+ }
1565
+
1566
+ // 为像素相关输入框添加实时预览和编辑跟踪
1567
+ existingPixelInputIds.forEach((inputId) => {
1568
+ const input = document.getElementById(inputId);
1569
+ if (input) {
1570
+ // 添加实时预览功能
1571
+ input.addEventListener("input", function () {
1572
+ const property = getPropertyFromInputId(inputId);
1573
+ styleEditTracker.markAsEdited(property);
1574
+
1575
+ // 实时应用样式
1576
+ applyStyleRealtime(property, this.value, this);
1577
+ });
1578
+
1579
+ // 失焦时处理单位添加和输入框值更新
1580
+ input.addEventListener("blur", function () {
1581
+ const value = this.value.trim();
1582
+ if (value) {
1583
+ const property = getPropertyFromInputId(inputId);
1584
+ const processedValue = StyleUtils.addUnitIfNeeded(value, property);
1585
+
1586
+ // 更新输入框显示处理后的值
1587
+ if (processedValue !== value) {
1588
+ this.value = processedValue;
1589
+ }
1590
+
1591
+ // 确保样式已正确应用
1592
+ element.style[property] = processedValue;
1593
+ }
1594
+ });
1595
+ }
1596
+ });
1597
+
1598
+ // 为其他输入框添加实时预览和编辑跟踪
1599
+ ['line-height', 'font-weight', 'text-align', 'color-text', 'bg-color-text'].forEach(inputId => {
1600
+ const input = document.getElementById(inputId);
1601
+ if (input) {
1602
+ input.addEventListener("input", function () {
1603
+ const property = getPropertyFromInputId(inputId);
1604
+ styleEditTracker.markAsEdited(property);
1605
+
1606
+ // 实时应用样式
1607
+ applyStyleRealtime(property, this.value, this);
1608
+ });
1609
+
1610
+ // 为select元素添加change事件(针对font-weight和text-align)
1611
+ if (input.tagName.toLowerCase() === 'select') {
1612
+ input.addEventListener("change", function () {
1613
+ const property = getPropertyFromInputId(inputId);
1614
+ styleEditTracker.markAsEdited(property);
1615
+
1616
+ // 实时应用样式
1617
+ applyStyleRealtime(property, this.value, this);
1618
+ });
1619
+ }
1620
+ }
1621
+ });
1622
+
1623
+ // 设置颜色选择器同步事件(带编辑跟踪和实时预览)
1624
+ setupColorSyncEventsWithRealtimePreview(element);
1625
+
1626
+ // 应用样式 - 发送到服务器保存
1627
+ const applyBtn = document.getElementById("apply-styles");
1628
+ if (applyBtn) {
1629
+ applyBtn.addEventListener("click", () => {
1630
+ saveEditedStylesToServer(currentStyleEditorElement || element, allStyleInputs);
1631
+ });
1632
+ }
1633
+
1634
+ // 重置样式
1635
+ const resetBtn = document.getElementById("reset-styles");
1636
+ if (resetBtn) {
1637
+ resetBtn.addEventListener("click", () => {
1638
+ const targetElement = currentStyleEditorElement || element;
1639
+ targetElement.style.cssText = "";
1640
+ UIHelper.showToast("元素样式已重置", "info");
1641
+ updateStyleEditor(targetElement);
1642
+ });
1643
+ }
1644
+
1645
+ // 取消
1646
+ const cancelBtn = document.getElementById("cancel-styles");
1647
+ if (cancelBtn) {
1648
+ cancelBtn.addEventListener("click", () => {
1649
+ // 恢复原始样式(如果需要的话)
1650
+ currentStyleEditorElement = null;
1651
+ styleEditTracker.reset();
1652
+ styleEditor.remove();
1653
+ });
1654
+ }
1655
+
1656
+ // ESC键关闭
1657
+ const handleEscape = (e) => {
1658
+ if (e.key === "Escape") {
1659
+ currentStyleEditorElement = null;
1660
+ styleEditTracker.reset();
1661
+ styleEditor.remove();
1662
+ document.removeEventListener("keydown", handleEscape);
1663
+ }
1664
+ };
1665
+ document.addEventListener("keydown", handleEscape);
1666
+ }
1667
+
1668
+ /**
1669
+ * 从输入框ID获取对应的CSS属性名
1670
+ */
1671
+ function getPropertyFromInputId(inputId) {
1672
+ const mapping = {
1673
+ "style-width": "width",
1674
+ "style-height": "height",
1675
+ "margin-top": "marginTop",
1676
+ "margin-right": "marginRight",
1677
+ "margin-bottom": "marginBottom",
1678
+ "margin-left": "marginLeft",
1679
+ "padding-top": "paddingTop",
1680
+ "padding-right": "paddingRight",
1681
+ "padding-bottom": "paddingBottom",
1682
+ "padding-left": "paddingLeft",
1683
+ "font-size": "fontSize",
1684
+ "line-height": "lineHeight",
1685
+ "font-weight": "fontWeight",
1686
+ "text-align": "textAlign",
1687
+ "color-text": "color",
1688
+ "bg-color-text": "backgroundColor"
1689
+ };
1690
+
1691
+ return mapping[inputId] || inputId.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
1692
+ }
1693
+
1694
+ // 设置颜色选择器同步事件(带编辑跟踪和实时预览)
1695
+ function setupColorSyncEventsWithRealtimePreview(element) {
1696
+ const colorPicker = document.getElementById("color-picker");
1697
+ const colorText = document.getElementById("color-text");
1698
+ const bgColorPicker = document.getElementById("bg-color-picker");
1699
+ const bgColorText = document.getElementById("bg-color-text");
1700
+ const bgColorStatus = document.getElementById("bg-color-status");
1701
+
1702
+ // 更新背景颜色状态显示
1703
+ function updateBackgroundColorStatus(value) {
1704
+ if (bgColorStatus) {
1705
+ bgColorStatus.textContent = StyleUtils.getBackgroundColorDisplayText(value);
1706
+ }
1707
+ }
1708
+
1709
+ // 实时应用颜色样式
1710
+ function applyColorRealtime(property, value) {
1711
+ try {
1712
+ const trimmedValue = (value || '').trim();
1713
+ if (trimmedValue) {
1714
+ element.style[property] = trimmedValue;
1715
+ } else {
1716
+ element.style[property] = "";
1717
+ }
1718
+ } catch (error) {
1719
+ console.warn('实时颜色应用失败:', property, value, error);
1720
+ }
1721
+ }
1722
+
1723
+ // 文字颜色选择器 - 只有支持文字样式的元素才会有这些元素
1724
+ if (colorPicker && colorText) {
1725
+ colorPicker.addEventListener("input", (e) => {
1726
+ const colorValue = e.target.value;
1727
+ colorText.value = colorValue;
1728
+ styleEditTracker.markAsEdited("color");
1729
+
1730
+ // 实时应用文字颜色
1731
+ applyColorRealtime("color", colorValue);
1732
+ });
1733
+
1734
+ colorText.addEventListener("input", (e) => {
1735
+ const inputValue = e.target.value.trim();
1736
+ try {
1737
+ const hexValue = StyleUtils.rgbToHex(inputValue);
1738
+ if (hexValue !== "#ffffff" || inputValue.includes("#")) {
1739
+ colorPicker.value = hexValue;
1740
+ }
1741
+ } catch (err) {
1742
+ console.warn("颜色转换错误:", err);
1743
+ }
1744
+ styleEditTracker.markAsEdited("color");
1745
+
1746
+ // 实时应用文字颜色
1747
+ applyColorRealtime("color", inputValue);
1748
+ });
1749
+ }
1750
+
1751
+ // 背景颜色选择器 - 所有元素都有
1752
+ if (bgColorPicker && bgColorText) {
1753
+ bgColorPicker.addEventListener("input", (e) => {
1754
+ const colorValue = e.target.value;
1755
+ bgColorText.value = colorValue;
1756
+ updateBackgroundColorStatus(colorValue);
1757
+ styleEditTracker.markAsEdited("backgroundColor");
1758
+
1759
+ // 实时应用背景颜色
1760
+ applyColorRealtime("backgroundColor", colorValue);
1761
+ });
1762
+
1763
+ bgColorText.addEventListener("input", (e) => {
1764
+ const inputValue = e.target.value.trim();
1765
+ try {
1766
+ if (inputValue) {
1767
+ const hexValue = StyleUtils.rgbToHex(inputValue);
1768
+ if (hexValue !== "#ffffff" || inputValue.includes("#")) {
1769
+ bgColorPicker.value = hexValue;
1770
+ }
1771
+ updateBackgroundColorStatus(inputValue);
1772
+ } else {
1773
+ // 如果输入为空,显示"无"
1774
+ updateBackgroundColorStatus('');
1775
+ }
1776
+ } catch (err) {
1777
+ console.warn("背景颜色转换错误:", err);
1778
+ updateBackgroundColorStatus(inputValue);
1779
+ }
1780
+ styleEditTracker.markAsEdited("backgroundColor");
1781
+
1782
+ // 实时应用背景颜色
1783
+ applyColorRealtime("backgroundColor", inputValue);
1784
+ });
1785
+
1786
+ // 监听失焦事件,处理清空输入的情况
1787
+ bgColorText.addEventListener("blur", (e) => {
1788
+ const inputValue = e.target.value.trim();
1789
+ updateBackgroundColorStatus(inputValue);
1790
+
1791
+ // 确保样式已正确应用
1792
+ applyColorRealtime("backgroundColor", inputValue);
1793
+ });
1794
+ }
1795
+ }
1796
+
1797
+ // 保存编辑过的样式到服务器(原来的applyEditedStylesToElement函数重命名)
1798
+ function saveEditedStylesToServer(element, allStyleInputs) {
1799
+ // 获取只有用户编辑过的样式
1800
+ const editedStyles = styleEditTracker.getEditedStyles(allStyleInputs);
1801
+
1802
+ // 如果没有编辑过的样式,显示提示并返回
1803
+ if (Object.keys(editedStyles).length === 0) {
1804
+ UIHelper.showToast("没有检测到样式修改", "info");
1805
+ return;
1806
+ }
1807
+
1808
+ const debugFile = element.getAttribute("data-debug-file");
1809
+ const debugLine = element.getAttribute("data-debug-line");
1810
+ const appliedStyles = {};
1811
+
1812
+ // 只处理被编辑过的样式,收集当前应用的样式值
1813
+ Object.keys(editedStyles).forEach((property) => {
1814
+ const elementId = allStyleInputs[property];
1815
+ const input = document.getElementById(elementId);
1816
+
1817
+ if (input && !input.disabled) {
1818
+ const value = input.value.trim();
1819
+
1820
+ if (value) {
1821
+ const processedValue = StyleUtils.addUnitIfNeeded(value, property);
1822
+ appliedStyles[property] = processedValue;
1823
+
1824
+ // 更新输入框显示(如果需要)
1825
+ if (processedValue !== value && processedValue.endsWith("px")) {
1826
+ input.value = processedValue;
1827
+ }
1828
+ } else {
1829
+ // 记录清除操作
1830
+ appliedStyles[property] = "";
1831
+ }
1832
+ }
1833
+ });
1834
+
1835
+ // 发送样式修改信息到服务端
1836
+ if (Object.keys(appliedStyles).length > 0) {
1837
+ // 创建详细的编辑信息
1838
+ const editInfo = {
1839
+ totalEditedProperties: styleEditTracker.editedProperties.size,
1840
+ appliedProperties: Object.keys(appliedStyles),
1841
+ editedProperties: Array.from(styleEditTracker.editedProperties)
1842
+ };
1843
+
1844
+ sendStyleEditToServer({
1845
+ file: debugFile,
1846
+ line: debugLine,
1847
+ element: {
1848
+ tag: element.tagName.toLowerCase(),
1849
+ id: element.id,
1850
+ className: element.className,
1851
+ },
1852
+ styles: appliedStyles,
1853
+ editInfo: editInfo // 添加编辑统计信息
1854
+ });
1855
+
1856
+ // 显示详细的保存结果
1857
+ const appliedCount = Object.keys(appliedStyles).length;
1858
+ UIHelper.showToast(`已保存 ${appliedCount} 个样式修改到文件`, "success");
1859
+ } else {
1860
+ UIHelper.showToast("没有有效的样式修改需要保存", "info");
1861
+ }
1862
+ }
1863
+
1864
+ // 页面点击事件处理
1865
+ function handleClick(event) {
1866
+ console.log("🐛 handleClick 被调用,debugMode:", debugMode);
1867
+
1868
+ if (!debugMode) {
1869
+ console.log("🐛 调试模式未开启,跳过处理");
1870
+ return;
1871
+ }
1872
+
1873
+ const element = event.target;
1874
+ console.log("🐛 点击的元素:", element);
1875
+
1876
+ // 排除调试工具自身注入的元素
1877
+ if (isDebugElement(element)) {
1878
+ console.log("🐛 点击的是调试元素,跳过处理");
1879
+ return;
1880
+ }
1881
+
1882
+ console.log("🐛 开始处理元素点击,准备显示调试信息");
1883
+ event.preventDefault();
1884
+ event.stopPropagation();
1885
+
1886
+ highlightElement(element);
1887
+ showDebugInfo(element);
1888
+ }
1889
+
1890
+ // 检查元素是否为调试工具注入的元素
1891
+ function isDebugElement(element) {
1892
+ // 调试元素ID列表
1893
+ const debugIds = [
1894
+ "pug-debug-toggle", "pug-debug-overlay", "pug-debug-highlight",
1895
+ "pug-style-editor", "pug-debug-toast"
1896
+ ];
1897
+
1898
+ // 检查元素本身是否为调试元素
1899
+ if (debugIds.includes(element.id) || element.className?.includes("pug-debug")) {
1900
+ return true;
1901
+ }
1902
+
1903
+ // 检查元素是否在调试容器内
1904
+ let parent = element.parentElement;
1905
+ while (parent) {
1906
+ if (debugIds.includes(parent.id) || parent.className?.includes("pug-debug")) {
1907
+ return true;
1908
+ }
1909
+ parent = parent.parentElement;
1910
+ }
1911
+
1912
+ return false;
1913
+ }
1914
+
1915
+ // 初始化调试工具
1916
+ function initDebugTool() {
1917
+ console.log("🐛 initDebugTool 开始初始化");
1918
+ createDebugToggle();
1919
+
1920
+ // 添加全局点击事件监听
1921
+ document.addEventListener("click", handleClick, true);
1922
+ console.log("🐛 已添加全局点击事件监听");
1923
+
1924
+ // ESC键关闭调试模式
1925
+ document.addEventListener("keydown", function (event) {
1926
+ if (event.key === "Escape" && debugMode) {
1927
+ toggleDebugMode();
1928
+ }
1929
+ });
1930
+
1931
+ window.addEventListener("scroll", updateHighlightPosition);
1932
+ window.addEventListener("resize", updateHighlightPosition);
1933
+
1934
+ console.log("🐛 Pug调试工具已加载,点击右下角按钮开启调试模式");
1935
+ }
1936
+
1937
+ // DOM加载完成后初始化
1938
+ if (document.readyState === "loading") {
1939
+ document.addEventListener("DOMContentLoaded", initDebugTool);
1940
+ } else {
1941
+ initDebugTool();
1942
+ }
1943
+
1944
+ // 更新样式编辑器内容
1945
+ function updateStyleEditor(element) {
1946
+ const styleEditor = document.getElementById("pug-style-editor");
1947
+ if (!styleEditor || !currentStyleEditorElement) {
1948
+ return;
1949
+ }
1950
+
1951
+ // 重新为新元素生成完整的样式编辑器
1952
+ currentStyleEditorElement = element;
1953
+ const computedStyle = window.getComputedStyle(element);
1954
+
1955
+ // 获取元素的实际样式值
1956
+ function getStyleValue(property, computedValue) {
1957
+ return StyleUtils.getStyleValue(element, property, computedValue);
1958
+ }
1959
+
1960
+ // 重新生成内容
1961
+ const contentDiv = document.getElementById("pug-style-editor-content");
1962
+ if (contentDiv) {
1963
+ const newContent = createStyleEditorContent(element, computedStyle, getStyleValue);
1964
+ contentDiv.innerHTML = newContent;
1965
+
1966
+ // 重新设置事件监听器
1967
+ setupStyleEditorEvents(element, styleEditor);
1968
+
1969
+ UIHelper.showToast(`样式编辑器已更新为新元素 <${element.tagName.toLowerCase()}>`, "info");
1970
+ }
1971
+ }
1972
+
1973
+ // 监听窗口滚动和大小改变,更新高亮框位置
1974
+ function updateHighlightPosition() {
1975
+ if (
1976
+ currentHighlightedElement &&
1977
+ highlightOverlay &&
1978
+ highlightOverlay.style.display === "block"
1979
+ ) {
1980
+ const rect = currentHighlightedElement.getBoundingClientRect();
1981
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
1982
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
1983
+
1984
+ highlightOverlay.style.left = rect.left + scrollLeft + "px";
1985
+ highlightOverlay.style.top = rect.top + scrollTop + "px";
1986
+ highlightOverlay.style.width = rect.width + "px";
1987
+ highlightOverlay.style.height = rect.height + "px";
1988
+ }
1989
+ }
1990
+
1991
+ // 发送调试信息到服务端
1992
+ function sendDebugInfoToServer(debugData) {
1993
+ fetch("/api/pug-debug", {
1994
+ method: "POST",
1995
+ headers: {
1996
+ "Content-Type": "application/json",
1997
+ },
1998
+ body: JSON.stringify({
1999
+ timestamp: new Date().toISOString(),
2000
+ url: window.location.href,
2001
+ ...debugData,
2002
+ }),
2003
+ }).catch((err) => {
2004
+ console.warn("发送调试信息失败:", err);
2005
+ });
2006
+ }
2007
+
2008
+ // 发送样式编辑信息到服务端
2009
+ function sendStyleEditToServer(styleData) {
2010
+ fetch("/api/pug-debug/style", {
2011
+ method: "POST",
2012
+ headers: {
2013
+ "Content-Type": "application/json",
2014
+ },
2015
+ body: JSON.stringify({
2016
+ timestamp: new Date().toISOString(),
2017
+ url: window.location.href,
2018
+ ...styleData,
2019
+ }),
2020
+ })
2021
+ .then((response) => response.json())
2022
+ .then((data) => {
2023
+ if (data.success) {
2024
+ console.log("样式修改成功:", data);
2025
+ // 可以选择显示成功提示,但为了避免过多提示,这里只记录日志
2026
+ } else {
2027
+ console.error("样式修改失败:", data);
2028
+ UIHelper.showToast(data.message || "样式修改处理失败", "error");
2029
+ }
2030
+ })
2031
+ .catch((err) => {
2032
+ console.error("发送样式修改失败:", err);
2033
+ UIHelper.showToast("发送样式修改请求失败", "error");
2034
+ });
2035
+ }
2036
+
2037
+ // 发送文本编辑信息到服务端
2038
+ function sendTextEditToServer(editData) {
2039
+ fetch("/api/pug-debug/edit", {
2040
+ method: "POST",
2041
+ headers: {
2042
+ "Content-Type": "application/json",
2043
+ },
2044
+ body: JSON.stringify({
2045
+ timestamp: new Date().toISOString(),
2046
+ url: window.location.href,
2047
+ ...editData,
2048
+ }),
2049
+ })
2050
+ .then((response) => response.json())
2051
+ .then((data) => {
2052
+ if (data.success) {
2053
+ UIHelper.showToast(data.message || "文件已成功更新", "success");
2054
+ console.log("文本编辑成功:", data);
2055
+ } else {
2056
+ UIHelper.showToast(data.message || "文件更新失败", "error");
2057
+ console.error("文本编辑失败:", data);
2058
+ }
2059
+ })
2060
+ .catch((err) => {
2061
+ UIHelper.showToast("发送编辑请求失败", "error");
2062
+ console.error("发送文本编辑失败:", err);
2063
+ });
2064
+ }
2065
+ }
2066
+
2067
+ /**
2068
+ * 生成客户端调试脚本
2069
+ * @returns {string} 返回调试脚本的HTML字符串
2070
+ */
2071
+ export function generateDebugScript() {
2072
+ return `
2073
+ <script>
2074
+ (${createDebugToolScript.toString()})();
2075
+ </script>
2076
+ `;
2077
+ }
2078
+
2079
+ /**
2080
+ * 为HTML内容注入调试脚本
2081
+ * @param {string} html - 原始HTML内容
2082
+ * @param {boolean} enableDebug - 是否启用调试功能,默认true
2083
+ * @returns {string} 注入调试脚本后的HTML内容
2084
+ */
2085
+ export function injectDebugScript(html, enableDebug = true) {
2086
+ if (!enableDebug) {
2087
+ return html;
2088
+ }
2089
+
2090
+ const debugScript = generateDebugScript();
2091
+
2092
+ // 尝试在 </body> 标签前插入调试脚本
2093
+ if (html.includes("</body>")) {
2094
+ return html.replace("</body>", `${debugScript}\n</body>`);
2095
+ }
2096
+
2097
+ // 如果没有 </body> 标签,尝试在 </html> 标签前插入
2098
+ if (html.includes("</html>")) {
2099
+ return html.replace("</html>", `${debugScript}\n</html>`);
2100
+ }
2101
+
2102
+ // 如果都没有,直接在末尾添加
2103
+ return html + debugScript;
2104
+ }