From c22c788cfad2800379086be15afefc19f12044c6 Mon Sep 17 00:00:00 2001 From: klarkxy <278370456@qq.com> Date: Wed, 8 Jul 2026 21:13:13 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ui):=20=E5=88=A4=E9=A2=98?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E9=A1=B5=E6=96=B0=E5=A2=9E=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E5=AF=B9=E6=AF=94=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 parse_diff_blocks 系列函数,将 runtimeinfo 解析为结构化的测试点对比块,支持 simple 表格和 diff -y 两种格式 - 在判题结果页插入"输出对比"区块,逐测试点并列展示期望输出与用户输出,差异行高亮 - 补充 diff-section 相关样式,移动端切换为单列布局 --- web/reinfo.php | 108 ++++++++++++++++++++++++ web/template/syzoj/reinfo.php | 152 ++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+) diff --git a/web/reinfo.php b/web/reinfo.php index 898443e..37f88a7 100644 --- a/web/reinfo.php +++ b/web/reinfo.php @@ -203,6 +203,9 @@ if($ok){ $view_reinfo = $view_reinfo_summary; + // 尝试解析为结构化的 diff 数据(用于 WA/PE/AC 时的对比展示) + $diff_blocks = parse_diff_blocks($raw_error); + // 管理员或 source_browser 永远看完整原文 $show_raw = $is_admin_session || $is_source_browser; if ($show_raw) { @@ -212,6 +215,111 @@ if($ok){ } } +/** + * 把 runtimeinfo.error 解析为结构化的 diff blocks + * 返回 [['name' => 'test1.out', 'expected' => [...], 'yours' => [...], 'full_mode' => bool], ...] + * 解析失败返回空数组 + */ +function parse_diff_blocks($text) { + $blocks = array(); + if (empty($text)) return $blocks; + + // 先按 "\n\n" 切分(simple mode 每个测试点之间有空行) + // 但要避免切分 full mode 里的内容 + $chunks = preg_split('/\n\s*\n/', $text); + foreach ($chunks as $chunk) { + $chunk = trim($chunk); + if ($chunk === '') continue; + + // full mode: 形如 "========[test1.out]=========" 开头 + if (preg_match('/^=+\[([^\]]+)\]=+$/m', $chunk, $m_head)) { + $name = $m_head[1]; + // 提取 "------diff out 200 lines-----" 后面的内容 + if (preg_match('/------diff out[^\n]*-+\n(.*?)\n=+\s*$/s', $chunk, $m_diff)) { + $diff_text = trim($m_diff[1]); + } else { + continue; // 没找到 diff 段,跳过 + } + $lines = parse_diff_side_by_side($diff_text); + if (!empty($lines)) { + $blocks[] = array( + 'name' => $name, + 'expected' => $lines['expected'], + 'yours' => $lines['yours'], + 'full_mode' => true, + ); + } + continue; + } + + // simple mode: 形如 "test1.out\n--\n|Expected|Yours\n|--|--\n|..." + if (preg_match('/^([^\n|]+)\n--\n\|Expected\|Yours\n\|--\|--\n(.*)$/s', $chunk, $m)) { + $name = trim($m[1]); + $table = $m[2]; + $lines = parse_pipe_diff_table($table); + if (!empty($lines)) { + $blocks[] = array( + 'name' => $name, + 'expected' => $lines['expected'], + 'yours' => $lines['yours'], + 'full_mode' => false, + ); + } + } + } + return $blocks; +} + +/** + * 解析 "|expected|yours\n" 格式的表格 + * 已知边角:每行以 | 开头和 | 分隔;空字段表示没有该行 + */ +function parse_pipe_diff_table($text) { + $expected = array(); + $yours = array(); + $lines = explode("\n", $text); + foreach ($lines as $line) { + if (strlen($line) < 2 || $line[0] !== '|') continue; + // 形如 "|a|b" 拆出 a 和 b + $rest = substr($line, 1); + // 找第一个 | + $pos = strpos($rest, '|'); + if ($pos === false) continue; + $left = substr($rest, 0, $pos); + $right = substr($rest, $pos + 1); + $expected[] = $left; + $yours[] = $right; + } + if (empty($expected)) return array(); + return array('expected' => $expected, 'yours' => $yours); +} + +/** + * 解析 `diff -y` 格式: " < expected > yours" + * 中间用 | 或 / 分隔(取决于 GNU diff 版本) + */ +function parse_diff_side_by_side($text) { + $expected = array(); + $yours = array(); + $lines = explode("\n", $text); + foreach ($lines as $line) { + // 找中间的 | 或 / (GNU diff -y 格式) + // 形如 " line1 | line2 " 或 " line1 < line2 " + if (preg_match('/^(.*?)\s+[<>|]\s+(.*)$/', $line, $m)) { + $expected[] = $m[1]; + $yours[] = $m[2]; + } elseif (trim($line) === '') { + // 空行:两边都空 + } else { + // 单边(一边空):整行属于另一边 + $expected[] = $line; + $yours[] = ''; + } + } + if (empty($expected)) return array(); + return array('expected' => $expected, 'yours' => $yours); +} + // 不是本人的提交,且不是 source_browser else{ if($spj[0][0]!=2) $view_errors = $MSG_WARNING_ACCESS_DENIED; diff --git a/web/template/syzoj/reinfo.php b/web/template/syzoj/reinfo.php index d18d826..f804940 100644 --- a/web/template/syzoj/reinfo.php +++ b/web/template/syzoj/reinfo.php @@ -133,6 +133,99 @@ i.icon.spinning { color: #2185d0; font-weight: 600; } + +/* Diff 对比样式 */ +#diff-section { + margin-bottom: 18px; +} +.diff-card { + margin-bottom: 14px; + border-left: 4px solid #fbbd08; + padding: 12px 16px; +} +.diff-header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 10px; + padding-bottom: 8px; + border-bottom: 1px solid #eee; + font-weight: 600; + color: #555; +} +.diff-name { + font-family: 'Fira Mono', 'Cascadia Code', Consolas, monospace; + font-size: 0.95em; +} +.diff-table { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} +@media (max-width: 768px) { + .diff-table { grid-template-columns: 1fr; } +} +.diff-side { + background: #fafafa; + border-radius: 6px; + overflow: hidden; + border: 1px solid #e0e0e0; +} +.diff-side-header { + padding: 6px 10px; + font-size: 0.85em; + font-weight: 600; + color: #fff; +} +.diff-expected .diff-side-header { + background: #21ba45; +} +.diff-yours .diff-side-header { + background: #db2828; +} +.diff-content { + margin: 0 !important; + padding: 8px 0 !important; + background: #fff !important; + font-family: 'Fira Mono', 'Cascadia Code', Consolas, monospace; + font-size: 0.85em; + line-height: 1.5; + overflow-x: auto; + max-height: 400px; + overflow-y: auto; +} +.diff-line { + display: flex; + align-items: flex-start; + padding: 0; + white-space: pre; +} +.diff-line-mismatch { + background: rgba(255, 230, 100, 0.25); +} +.diff-expected .diff-line-mismatch { + background: rgba(33, 186, 69, 0.12); +} +.diff-yours .diff-line-mismatch { + background: rgba(219, 40, 40, 0.10); +} +.diff-lineno { + display: inline-block; + min-width: 38px; + padding: 0 8px; + text-align: right; + color: #999; + background: #f5f5f5; + border-right: 1px solid #e8e8e8; + user-select: none; + flex-shrink: 0; +} +.diff-text { + padding: 0 10px; + flex: 1; + word-break: break-all; + white-space: pre-wrap; +}
' . $line_no . '' . htmlspecialchars($e, ENT_QUOTES, 'UTF-8') . '
' . $line_no . '' . htmlspecialchars($y, ENT_QUOTES, 'UTF-8') . '