496 lines
15 KiB
HTML
Executable File
496 lines
15 KiB
HTML
Executable File
{layout name="layout"}
|
|
<script type="text/javascript" src="__JS__/../libs/jquery.min.js"></script>
|
|
<style>
|
|
.debug-page {
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.debug-header {
|
|
background: #fbcbcf;
|
|
color: white;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.debug-section {
|
|
background: white;
|
|
border-radius: 4px;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.debug-code {
|
|
/* background: #272822;
|
|
color: #f8f8f2; */
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
margin: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.debug-code::before {
|
|
content: '点击复制';
|
|
position: absolute;
|
|
top: 5px;
|
|
right: 5px;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 3px;
|
|
font-size: 12px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.debug-code:hover::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
.debug-info {
|
|
line-height: 30px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.debug-info-label {
|
|
font-weight: bold;
|
|
color: #495057;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.debug-trace {
|
|
counter-reset: line;
|
|
}
|
|
|
|
.debug-trace-line {
|
|
position: relative;
|
|
padding-left: 3.5em;
|
|
transition: background-color 0.2s;
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.debug-trace-line::before {
|
|
counter-increment: line;
|
|
content: counter(line);
|
|
position: absolute;
|
|
left: 0;
|
|
width: 2.5em;
|
|
text-align: right;
|
|
color: #6c757d;
|
|
border-right: 1px solid #dee2e6;
|
|
padding-right: 1em;
|
|
}
|
|
.debug-trace-line.noline::before,.noline .debug-trace-line::before {
|
|
content: '';
|
|
width: 0;
|
|
}
|
|
.debug-trace-line.noline,.noline .debug-trace-line {
|
|
padding-left: 1em;
|
|
}
|
|
|
|
.debug-trace-line:hover {
|
|
background: #f8f9fa;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.debug-trace-file {
|
|
color: #28a745;
|
|
}
|
|
|
|
.debug-trace-line-number {
|
|
color: #dc3545;
|
|
}
|
|
|
|
.debug-trace-function {
|
|
color: #007bff;
|
|
}
|
|
|
|
.debug-trace-class {
|
|
color: #6f42c1;
|
|
}
|
|
|
|
.debug-actions {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.nav-tabs .nav-link {
|
|
color: #495057;
|
|
}
|
|
|
|
.nav-tabs .nav-item.active {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.tab-content {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-top: none;
|
|
border-radius: 0 0 4px 4px;
|
|
padding: 20px;
|
|
max-height: calc(100vh - 250px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.highlight {
|
|
background-color: #fff3cd;
|
|
padding: 2px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* 暗色模式 */
|
|
.dark-mode {
|
|
background: #1a1a1a;
|
|
color: #fff;
|
|
}
|
|
|
|
.dark-mode .debug-section,
|
|
.dark-mode .tab-content {
|
|
background: #2d2d2d;
|
|
}
|
|
|
|
.dark-mode .nav-tabs {
|
|
border-color: #444;
|
|
}
|
|
|
|
.dark-mode .nav-tabs .nav-link {
|
|
color: #fff;
|
|
}
|
|
|
|
.dark-mode .nav-tabs .nav-link.active {
|
|
background: #2d2d2d;
|
|
border-color: #444;
|
|
}
|
|
|
|
.dark-mode .debug-code {
|
|
background: #000;
|
|
}
|
|
|
|
.dark-mode .debug-trace-line:hover {
|
|
background: #333;
|
|
}
|
|
|
|
.dark-mode .debug-info-label {
|
|
color: #adb5bd;
|
|
}
|
|
</style>
|
|
<script>
|
|
// 搜索功能
|
|
function toggleSearch() {
|
|
const $searchBox = $('#search-box');
|
|
$searchBox.toggle();
|
|
if ($searchBox.is(':visible')) {
|
|
$('#search-input').focus();
|
|
}
|
|
}
|
|
|
|
function clearSearch() {
|
|
$('#search-input').val('');
|
|
removeHighlights();
|
|
}
|
|
|
|
$('#search-input').on('input', function() {
|
|
const searchText = $(this).val().toLowerCase();
|
|
removeHighlights();
|
|
if (searchText) {
|
|
highlightText(searchText);
|
|
}
|
|
});
|
|
|
|
function highlightText(text) {
|
|
const $content = $('.tab-content');
|
|
const regex = new RegExp(text, 'gi');
|
|
|
|
$content.contents().each(function() {
|
|
if (this.nodeType === Node.TEXT_NODE &&
|
|
!$(this).parent().is('script, style')) {
|
|
const $parent = $(this).parent();
|
|
const newText = this.textContent.replace(regex, match =>
|
|
`<span class="highlight">${match}</span>`
|
|
);
|
|
if (newText !== this.textContent) {
|
|
$parent.html(newText);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeHighlights() {
|
|
$('.highlight').each(function() {
|
|
const $parent = $(this).parent();
|
|
$parent.html($parent.text());
|
|
});
|
|
}
|
|
|
|
const errorInfo = {
|
|
message: '{$msg}',
|
|
code: '{$code}',
|
|
file: '{$file}',
|
|
line: '{$line}',
|
|
trace: `{$trace_str}`,
|
|
request: {
|
|
method: '{$method}',
|
|
url: '{$url}',
|
|
param: {:json_encode($post,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)},
|
|
get: {:json_encode($get,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)},
|
|
header: {:json_encode($header,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)},
|
|
},
|
|
environment: {
|
|
php_version: '{$Think.PHP_VERSION}',
|
|
thinkphp_version: '{$Think.THINK_VERSION}',
|
|
os: '{$Think.PHP_OS}',
|
|
debug_time: '{$Think.APP_DEBUG_TIME}'
|
|
}
|
|
};
|
|
// 复制功能
|
|
function fallbackCopyToClipboard(text) {
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = text;
|
|
textArea.style.position = 'fixed';
|
|
textArea.style.left = '-999999px';
|
|
textArea.style.top = '-999999px';
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
|
|
try {
|
|
document.execCommand('copy');
|
|
alert('已复制到剪贴板');
|
|
} catch (err) {
|
|
console.error('复制失败:', err);
|
|
alert('复制失败,请手动复制');
|
|
}
|
|
|
|
document.body.removeChild(textArea);
|
|
}
|
|
|
|
function copyToClipboard(text) {
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(text)
|
|
.then(() => alert('已复制到剪贴板'))
|
|
.catch(() => fallbackCopyToClipboard(text));
|
|
} else {
|
|
fallbackCopyToClipboard(text);
|
|
}
|
|
}
|
|
|
|
function copyErrorInfo() {
|
|
copyToClipboard(JSON.stringify(errorInfo, null, 2));
|
|
}
|
|
|
|
// 导出功能
|
|
function exportError() {
|
|
const blob = new Blob([JSON.stringify(errorInfo, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `error-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
// 暗色模式
|
|
function toggleDarkMode() {
|
|
$('body').toggleClass('dark-mode');
|
|
localStorage.setItem('debug-dark-mode', $('body').hasClass('dark-mode'));
|
|
}
|
|
|
|
// 初始化
|
|
$(document).ready(function() {
|
|
// 检查暗色模式
|
|
if (localStorage.getItem('debug-dark-mode') === 'true') {
|
|
$('body').addClass('dark-mode');
|
|
}
|
|
|
|
// 为所有代码块添加点击复制功能
|
|
$('.debug-code').on('click', function() {
|
|
copyToClipboard($(this).text());
|
|
});
|
|
|
|
// 记住最后打开的标签页
|
|
const lastTab = localStorage.getItem('debug-last-tab');
|
|
if (lastTab) {
|
|
const $tab = $(`#${lastTab}-tab`);
|
|
if ($tab.length) {
|
|
$tab.tab('show');
|
|
}
|
|
}
|
|
|
|
// 保存标签页状态
|
|
$('.nav-link').on('shown.bs.tab', function(e) {
|
|
localStorage.setItem('debug-last-tab', $(e.target).attr('id').replace('-tab', ''));
|
|
});
|
|
});
|
|
</script>
|
|
<div class="debug-page">
|
|
<!-- 错误标题 -->
|
|
<div class="debug-header">
|
|
<h4 class="mb-0">
|
|
<i class="fas fa-exclamation-triangle mr-2"></i>
|
|
{$msg}
|
|
</h4>
|
|
</div>
|
|
|
|
<!-- 标签页导航 -->
|
|
<ul class="nav nav-tabs" id="debugTabs" role="tablist">
|
|
<li class="nav-item active">
|
|
<a class="nav-link" id="basic-tab" data-toggle="tab" href="#basic" role="tab">基本信息</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="request-tab" data-toggle="tab" href="#request" role="tab">请求信息</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="trace-tab" data-toggle="tab" href="#trace" role="tab">堆栈跟踪</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="env-tab" data-toggle="tab" href="#env" role="tab">环境信息</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="details-tab" data-toggle="tab" href="#details" role="tab">详细错误</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- 标签页内容 -->
|
|
<div class="tab-content" id="debugTabContent">
|
|
<!-- 基本信息 -->
|
|
<div class="tab-pane fade active in" id="basic" role="tabpanel">
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">错误代码:</span>
|
|
<span class="badge badge-danger">{$code}</span>
|
|
</div>
|
|
<div class="debug-info" style="display: flex;align-items: center;">
|
|
<span class="debug-info-label">SQL:</span>
|
|
<div class="debug-trace noline" style="flex:1;">
|
|
<div class="debug-trace-line">
|
|
<span class="debug-trace-file" onclick="copyToClipboard('{$file}')">{$file}</span>
|
|
<span class="debug-trace-line-number">:{$line}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{if isset($data) && isset($data['Database Status'])}
|
|
<div class="debug-info" style="display: flex;align-items: center;">
|
|
<span class="debug-info-label">SQL:</span>
|
|
<pre class="debug-code" style="flex:1;">{$data['Database Status']['Error SQL']}</pre>
|
|
</div>
|
|
{/if}
|
|
{if condition="$previous"}
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">上一个错误:</span>
|
|
<span>{$previous}</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- 请求信息 -->
|
|
<div class="tab-pane fade" id="request" role="tabpanel">
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">请求方法:</span>
|
|
<span class="badge badge-info">{$method}</span>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">请求URL:</span>
|
|
<span onclick="copyToClipboard('{$url}')">{$url}</span>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">GET参数:</span>
|
|
<pre class="debug-code">{:json_encode($get,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)}</pre>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">POST参数:</span>
|
|
<pre class="debug-code">{:json_encode($post,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)}</pre>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">请求头:</span>
|
|
<pre class="debug-code">{:json_encode($header,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 堆栈跟踪 -->
|
|
<div class="tab-pane fade" id="trace" role="tabpanel">
|
|
<div class="debug-trace">
|
|
{volist name="trace" id="item"}
|
|
<div class="debug-trace-line">
|
|
<span class="debug-trace-file" onclick="copyToClipboard('{$item.file}')">{$item.file}</span>
|
|
<span class="debug-trace-line-number">:{$item.line}</span>
|
|
<span class="debug-trace-function">{$item.function}</span>
|
|
{if condition="$item.class"}
|
|
<span class="debug-trace-class">{$item.class}</span>
|
|
{/if}
|
|
</div>
|
|
{/volist}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 环境信息 -->
|
|
<div class="tab-pane fade" id="env" role="tabpanel">
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">PHP版本:</span>
|
|
<span>{$Think.PHP_VERSION}</span>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">ThinkPHP版本:</span>
|
|
<span>{$Think.THINK_VERSION}</span>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">服务器系统:</span>
|
|
<span>{$Think.PHP_OS}</span>
|
|
</div>
|
|
<div class="debug-info">
|
|
<span class="debug-info-label">运行时间:</span>
|
|
<span>{$Think.APP_DEBUG_TIME}秒</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 详细错误信息 -->
|
|
<div class="tab-pane fade" id="details" role="tabpanel">
|
|
<pre class="debug-code">{$trace_str}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 调试工具 -->
|
|
<div class="debug-actions">
|
|
<div class="btn-group">
|
|
<button class="btn btn-primary" onclick="window.location.reload()">
|
|
<i class="fas fa-sync-alt"></i> 刷新
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="history.back()">
|
|
<i class="fas fa-arrow-left"></i> 返回
|
|
</button>
|
|
<button class="btn btn-info" onclick="copyErrorInfo()">
|
|
<i class="fas fa-copy"></i> 复制
|
|
</button>
|
|
<button class="btn btn-warning" onclick="toggleSearch()">
|
|
<i class="fas fa-search"></i> 搜索
|
|
</button>
|
|
<button class="btn btn-success" onclick="exportError()">
|
|
<i class="fas fa-file-export"></i> 导出
|
|
</button>
|
|
<button class="btn btn-dark" onclick="toggleDarkMode()">
|
|
<i class="fas fa-moon"></i> 暗色
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 搜索框 -->
|
|
<div id="search-box" class="position-fixed" style="display: none; top: 20px; right: 20px; z-index: 1001;">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="search-input" placeholder="搜索错误信息...">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-secondary" onclick="clearSearch()">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> |