Files
im/plugin/admin/app/view/common/error.html
T
commie c153975eed 7
2026-01-08 05:42:44 +08:00

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>