fix(debug): 修复预览不实时更新、出错位置不生效、中文Unicode转义等问题,预览改为右侧弹出面板,增加字数统计

This commit is contained in:
augushong
2026-05-24 09:32:32 +08:00
parent 4668865b4f
commit 7312db717c
2 changed files with 100 additions and 36 deletions

3
.gitignore vendored
View File

@@ -48,4 +48,5 @@ extend/base/common/command/curd/migrate_output.php
/source/**/.vscode /source/**/.vscode
.trae/documents/ .trae/documents/
/docker-dev/ /docker-dev/
/.sisyphus /.sisyphus
/.omo

View File

@@ -322,6 +322,13 @@ if (!function_exists('echo_value')) {
margin-right: 0; margin-right: 0;
padding-right: 0; padding-right: 0;
} }
#char-count {
font-size: 12px;
color: #888;
min-width: 40px;
text-align: center;
font-family: Consolas, monospace;
}
#debug-toolbar .toolbar-group label { #debug-toolbar .toolbar-group label {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -372,25 +379,55 @@ if (!function_exists('echo_value')) {
} }
#markdown-preview { #markdown-preview {
display: none; display: none;
padding: 20px; position: fixed;
top: 0;
right: 0;
width: 45%;
height: 100%;
background: #fff; background: #fff;
position: relative; border-left: 1px solid #ddd;
margin-top: 20px; box-shadow: -4px 0 16px rgba(0,0,0,0.12);
border: 1px solid #ddd; z-index: 99998;
border-radius: 4px; box-sizing: border-box;
overflow: hidden;
}
#markdown-preview .preview-header {
padding: 12px 20px;
background: #f8f9fa;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: bold;
color: #333;
}
#markdown-preview .preview-header .close-btn {
cursor: pointer;
font-size: 20px;
color: #999;
background: none;
border: none;
padding: 0 4px;
line-height: 1;
}
#markdown-preview .preview-header .close-btn:hover {
color: #333;
} }
#markdown-preview pre { #markdown-preview pre {
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 13px; font-size: 13px;
background: #f8f9fa; background: #fff;
padding: 15px; padding: 20px;
border-radius: 4px; margin: 0;
border: 1px solid #e8e8e8; border: none;
border-radius: 0;
color: #333; color: #333;
max-height: 600px; height: calc(100% - 49px);
overflow: auto; overflow: auto;
box-sizing: border-box;
} }
</style> </style>
</head> </head>
@@ -517,6 +554,7 @@ if (!function_exists('echo_value')) {
<label data-dynamic="env" data-env-key="Server/Request Data"><input type="checkbox" data-key="env_server"> Server</label> <label data-dynamic="env" data-env-key="Server/Request Data"><input type="checkbox" data-key="env_server"> Server</label>
</span> </span>
<span class="toolbar-group"> <span class="toolbar-group">
<span id="char-count" title="当前选中内容的字符数">0字</span>
<button id="btn-copy" onclick="copyToClipboard()">复制给AI</button> <button id="btn-copy" onclick="copyToClipboard()">复制给AI</button>
<button id="btn-preview" onclick="togglePreview()">预览</button> <button id="btn-preview" onclick="togglePreview()">预览</button>
<button id="btn-path" onclick="togglePathMode()">路径:简略</button> <button id="btn-path" onclick="togglePathMode()">路径:简略</button>
@@ -526,8 +564,12 @@ if (!function_exists('echo_value')) {
<!-- Hidden root path --> <!-- Hidden root path -->
<div id="root-path" style="display:none;"><?php echo \think\facade\App::getRootPath(); ?></div> <div id="root-path" style="display:none;"><?php echo \think\facade\App::getRootPath(); ?></div>
<!-- Markdown Preview --> <!-- Markdown Preview (right-side slide-out panel) -->
<div id="markdown-preview"> <div id="markdown-preview">
<div class="preview-header">
<span>Markdown Preview</span>
<button class="close-btn" onclick="togglePreview()">&times;</button>
</div>
<pre id="markdown-content"></pre> <pre id="markdown-content"></pre>
</div> </div>
@@ -634,21 +676,24 @@ if (!function_exists('echo_value')) {
<?php <?php
// Message // Message
if (isset($message)) { if (isset($message)) {
$md = "# \u7cfb\u7edf\u53d1\u751f\u9519\u8bef\n\n## \u9519\u8bef\u4fe1\u606f\n> " . strip_tags((string)$message) . "\n\n"; $md = "# 系统发生错误\n\n## 错误信息\n> " . strip_tags((string)$message) . "\n\n";
echo 'mdFragments.message = ' . json_encode($md, JSON_UNESCAPED_UNICODE) . ";\n"; echo 'mdFragments.message = ' . json_encode($md, JSON_UNESCAPED_UNICODE) . ";\n";
} }
// Location // Location (from first trace)
if (isset($file) && isset($line)) { if (isset($traces) && is_array($traces) && !empty($traces)) {
$fileEscaped = str_replace('\\', '\\\\', $file); $firstTrace = $traces[0];
$md = "## \u51fa\u9519\u4f4d\u7f6e\n- **File:** `{$fileEscaped}`\n- **Line:** `{$line}`\n\n"; if (isset($firstTrace['file']) && isset($firstTrace['line'])) {
echo 'mdFragments.location = ' . json_encode($md, JSON_UNESCAPED_UNICODE) . ";\n"; $fileEscaped = str_replace('\\', '\\\\', $firstTrace['file']);
$md = "## 出错位置\n- **File:** `{$fileEscaped}`\n- **Line:** `{$firstTrace['line']}`\n\n";
echo 'mdFragments.location = ' . json_encode($md, JSON_UNESCAPED_UNICODE) . ";\n";
}
} }
// Call Stack (per trace) // Call Stack (per trace)
if (isset($traces) && is_array($traces)) { if (isset($traces) && is_array($traces)) {
foreach ($traces as $index => $trace) { foreach ($traces as $index => $trace) {
$md = "## \u5f02\u5e38 #{$index}\n"; $md = "## 异常 #{$index}\n";
if (isset($trace['message'])) { if (isset($trace['message'])) {
$md .= "**Message:** " . strip_tags($trace['message']) . "\n\n"; $md .= "**Message:** " . strip_tags($trace['message']) . "\n\n";
} }
@@ -656,12 +701,12 @@ if (isset($traces) && is_array($traces)) {
$md .= "**Location:** `{$trace['file']}` : {$trace['line']}\n\n"; $md .= "**Location:** `{$trace['file']}` : {$trace['line']}\n\n";
} }
if (!empty($trace['trace'])) { if (!empty($trace['trace'])) {
$md .= "### \u8c03\u7528\u6808\n"; $md .= "### 调用栈\n";
$count = 0; $count = 0;
$total = count($trace['trace']); $total = count($trace['trace']);
foreach ($trace['trace'] as $i => $item) { foreach ($trace['trace'] as $i => $item) {
if ($count >= 20) { if ($count >= 20) {
$md .= "... (\u5171 {$total} \u5c42\uff0c\u5df2\u622a\u65ad)\n"; $md .= "... ( {$total} 层,已截断)\n";
break; break;
} }
$func = $item['function'] ?? 'unknown'; $func = $item['function'] ?? 'unknown';
@@ -684,7 +729,7 @@ if (isset($traces) && is_array($traces)) {
if (isset($traces) && is_array($traces)) { if (isset($traces) && is_array($traces)) {
foreach ($traces as $index => $trace) { foreach ($traces as $index => $trace) {
if (!empty($trace['source'])) { if (!empty($trace['source'])) {
$md = "### \u6e90\u7801\u7247\u6bb5\n```php\n"; $md = "### 源码片段\n```php\n";
foreach ((array) $trace['source']['source'] as $key => $value) { foreach ((array) $trace['source']['source'] as $key => $value) {
$lineNum = $key + $trace['source']['first']; $lineNum = $key + $trace['source']['first'];
$marker = ($trace['line'] === $lineNum) ? ' >>>' : ''; $marker = ($trace['line'] === $lineNum) ? ' >>>' : '';
@@ -699,7 +744,7 @@ if (isset($traces) && is_array($traces)) {
// Exception Data // Exception Data
if (isset($datas) && is_array($datas)) { if (isset($datas) && is_array($datas)) {
$hasData = false; $hasData = false;
$md = "## \u5f02\u5e38\u6570\u636e\n"; $md = "## 异常数据\n";
foreach ($datas as $label => $value) { foreach ($datas as $label => $value) {
if (!empty($value)) { if (!empty($value)) {
$hasData = true; $hasData = true;
@@ -761,10 +806,14 @@ if (isset($tables) && is_array($tables)) {
} }
restoreCheckboxState(); restoreCheckboxState();
updateDisabledState(); updateDisabledState();
// Auto-save on checkbox change updatePreviewIfOpen();
// Auto-save on checkbox change + update preview if open
var checkboxes = document.querySelectorAll('#debug-toolbar input[type="checkbox"]'); var checkboxes = document.querySelectorAll('#debug-toolbar input[type="checkbox"]');
checkboxes.forEach(function(cb) { checkboxes.forEach(function(cb) {
cb.addEventListener('change', saveCheckboxState); cb.addEventListener('change', function() {
saveCheckboxState();
updatePreviewIfOpen();
});
}); });
} }
@@ -807,6 +856,19 @@ if (isset($tables) && is_array($tables)) {
} catch(e) {} } catch(e) {}
} }
// Update preview content if preview panel is currently open
function updatePreviewIfOpen() {
var preview = document.getElementById('markdown-preview');
var content = generateMarkdown();
var countEl = document.getElementById('char-count');
if (countEl) {
countEl.textContent = content.length + '字';
}
if (preview && preview.style.display === 'block') {
document.getElementById('markdown-content').textContent = content;
}
}
// Update disabled state for env checkboxes based on data availability // Update disabled state for env checkboxes based on data availability
function updateDisabledState() { function updateDisabledState() {
// Exception data // Exception data
@@ -863,20 +925,20 @@ if (isset($tables) && is_array($tables)) {
window.copyToClipboard = function() { window.copyToClipboard = function() {
var content = generateMarkdown(); var content = generateMarkdown();
if (!content.trim()) { if (!content.trim()) {
alert('\u8bf7\u81f3\u5c11\u52fe\u9009\u4e00\u4e2a\u5185\u5bb9\u9009\u9879'); alert('请至少勾选一个内容选项');
return; return;
} }
var btn = document.getElementById('btn-copy'); var btn = document.getElementById('btn-copy');
if (navigator.clipboard) { if (navigator.clipboard) {
navigator.clipboard.writeText(content).then(function() { navigator.clipboard.writeText(content).then(function() {
btn.textContent = '\u5df2\u590d\u5236'; btn.textContent = '已复制';
btn.classList.add('copied'); btn.classList.add('copied');
setTimeout(function() { setTimeout(function() {
btn.textContent = '\u590d\u5236\u7ed9AI'; btn.textContent = '复制给AI';
btn.classList.remove('copied'); btn.classList.remove('copied');
}, 2000); }, 2000);
}, function(err) { }, function(err) {
alert('\u590d\u5236\u5931\u8d25: ' + err); alert('复制失败: ' + err);
}); });
} else { } else {
var textarea = document.createElement('textarea'); var textarea = document.createElement('textarea');
@@ -887,14 +949,14 @@ if (isset($tables) && is_array($tables)) {
textarea.select(); textarea.select();
try { try {
document.execCommand('copy'); document.execCommand('copy');
btn.textContent = '\u5df2\u590d\u5236'; btn.textContent = '已复制';
btn.classList.add('copied'); btn.classList.add('copied');
setTimeout(function() { setTimeout(function() {
btn.textContent = '\u590d\u5236\u7ed9AI'; btn.textContent = '复制给AI';
btn.classList.remove('copied'); btn.classList.remove('copied');
}, 2000); }, 2000);
} catch (err) { } catch (err) {
alert('\u590d\u5236\u5931\u8d25'); alert('复制失败');
} }
document.body.removeChild(textarea); document.body.removeChild(textarea);
} }
@@ -909,11 +971,11 @@ if (isset($tables) && is_array($tables)) {
var content = generateMarkdown(); var content = generateMarkdown();
document.getElementById('markdown-content').textContent = content; document.getElementById('markdown-content').textContent = content;
preview.style.display = 'block'; preview.style.display = 'block';
btn.textContent = '\u5173\u95ed\u9884\u89c8'; btn.textContent = '关闭预览';
btn.classList.add('active'); btn.classList.add('active');
} else { } else {
preview.style.display = 'none'; preview.style.display = 'none';
btn.textContent = '\u9884\u89c8'; btn.textContent = '预览';
btn.classList.remove('active'); btn.classList.remove('active');
} }
}; };
@@ -923,13 +985,14 @@ if (isset($tables) && is_array($tables)) {
var btn = document.getElementById('btn-path'); var btn = document.getElementById('btn-path');
if (currentPathMode === 'simple') { if (currentPathMode === 'simple') {
currentPathMode = 'full'; currentPathMode = 'full';
btn.textContent = '\u8def\u5f84:\u5b8c\u6574'; btn.textContent = '路径:完整';
btn.classList.add('active'); btn.classList.add('active');
} else { } else {
currentPathMode = 'simple'; currentPathMode = 'simple';
btn.textContent = '\u8def\u5f84:\u7b80\u7565'; btn.textContent = '路径:简洁';
btn.classList.remove('active'); btn.classList.remove('active');
} }
updatePreviewIfOpen();
}; };
})(); })();
</script> </script>