$sc) { $t += 600; $list[] = [ 'idx' => $i + 1, 'submit_ts' => $t, 'score' => $sc, 'temp_save' => !empty($tempMarks[$i]), 'duration' => 120, ]; } return [ 'sid' => $sid, 'name' => $name, 'cls' => '1班', 'grp' => 'A', 'score' => end($scoresAsc), 'best_score' => $best, 'attempts' => count($scoresAsc), 'attempts_list' => $list, ]; } /* ---- 示例数据:与需求截图同口径(合格线 110,阈值 1 次) ---- */ $exams = [ 'm' => ['name' => '第一次月考·数学', 'rows' => [ sample_stu('356001', '张三', 136.0, [112.0, 100.0, 136.0, 127.0]), sample_stu('356002', '李四', 0.0, []), // 整场未考 sample_stu('356003', '王五', 95.0, [95.0]), // 未达合格线 → 不标绿 ]], 'e' => ['name' => '第一次月考·英语', 'rows' => [ sample_stu('356001', '张三', 112.0, [112.0, 99.0]), sample_stu('356002', '李四', 133.0, [130.0, 129.0, 133.0]), ]], ]; $opts = [ 'pass_line' => 110, // 本卷满分 130 × 合格率 85% 'pass_times' => 1, // 达标阈值 'mark_best' => true, // 最高分标红 'pass_row' => true, // 输出「合格次数」行 ]; /* ---- 1) 多考试块表 ---- */ $block = ny_assemble_block_sheet($exams, $opts); $blockBin = XlsxWriter::renderMulti([[ 'name' => '每次考试', 'rows' => $block['rows'], 'widths' => $block['widths'] ?? [], 'merges' => $block['merges'], 'header_rows' => $block['header_rows'], 'cell_styles' => $block['cell_styles'] ?? [], ]]); $blockPath = __DIR__ . '/../docs/samples/export-multi-block.xlsx'; file_put_contents($blockPath, $blockBin); /* ---- 2) 单场宽表(只导出一场考试时的版式) ---- */ $wideRes = ny_assemble_attempt_wide_sheet($exams['m'], $opts); $wideBin = XlsxWriter::renderMulti([[ 'name' => '每次考试', 'rows' => $wideRes['rows'], 'widths' => $wideRes['widths'] ?? [], 'merges' => $wideRes['merges'], 'header_rows' => $wideRes['header_rows'], 'cell_styles' => $wideRes['cell_styles'] ?? [], ]]); $widePath = __DIR__ . '/../docs/samples/export-single-wide.xlsx'; file_put_contents($widePath, $wideBin); /* ---- 3) 交给 Node 渲染预览 HTML(用 app.js 里的 sheetToHtml,避免两份实现漂移) ---- */ $data = [ 'block' => [ 'title' => '多考试导出 · 每次考试(两行表头)', 'sheet' => [ 'rows' => $block['rows'], 'merges' => $block['merges'], 'header_rows' => $block['header_rows'], 'cell_styles' => $block['cell_styles'] ?? [], ], 'meta' => [ 'pass_line' => $opts['pass_line'], 'pass_times' => $opts['pass_times'], ], ], 'wide' => [ 'title' => '单场导出 · 每次考试(每人一行的宽表)', 'sheet' => [ 'rows' => $wideRes['rows'], 'merges' => $wideRes['merges'], 'header_rows' => $wideRes['header_rows'], 'cell_styles' => $wideRes['cell_styles'] ?? [], ], 'meta' => [ 'pass_line' => $opts['pass_line'], 'pass_times' => $opts['pass_times'], 'exam_name' => $exams['m']['name'], ], ], ]; file_put_contents(__DIR__ . '/../docs/samples/sheet-data.json', json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); echo '块表:' . count($block['rows']) . ' 行 / ' . count($block['merges']) . ' 处合并 / ' . count($block['cell_styles'] ?? []) . " 处标记 → " . realpath($blockPath) . PHP_EOL; echo '宽表:' . count($wideRes['rows']) . ' 行 / ' . count($wideRes['merges']) . ' 处合并 / ' . count($wideRes['cell_styles'] ?? []) . " 处标记 → " . realpath($widePath) . PHP_EOL; echo '预览数据:' . realpath(__DIR__ . '/../docs/samples/sheet-data.json') . PHP_EOL;