feat(export/dashboard): 导出增强与大屏公开化
导出-每次考试/多考试对比: - 合格次数阈值等标记项抽为常显区块,三类成绩表共用 - 附缺考名单扩展到多考试导出(取所选考试全都没交的人) - 块表每场固定2列(成绩+合并空位),修复漏推空串导致的整行左移/串列 - 列宽按实际内容估算,合并区空位只给最小宽度 - 合格次数始终显示实际次数(含0),仅 ≥ 阈值标绿底 - 打印标题改为「成绩明细」,缺考名单分页单独成页 - 打印样式加 print-color-adjust:exact,绿底/红字可正常印出 数据大屏: - ?class= 班级大屏免登录公开访问,接口侧放行并按班级裁剪后回传 - 单班大屏移除退出按钮,仅首页保留 - 修复红绿判定真bug:membersTable 误用 window.DASH(DASH为let声明取不到) 改为 scoreGrade() 直接读自由变量,按后台合格率正确判红/黄/绿 测试/样例/文档同步更新,全量回归通过。
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* 生成「导出 / 打印」新版式的可打开样例:
|
||||
* 1) docs/samples/export-multi-block.xlsx —— 多考试块表(两行表头 + 合格次数 + 红绿标记)
|
||||
* 2) docs/samples/export-single-wide.xlsx —— 单场宽表(每人一行、第几次做列名)
|
||||
* 3) docs/samples/sheet-data.json —— 供 make-samples.js 渲染预览 HTML
|
||||
*
|
||||
* 数据是一次性写死的示例数据(与 tests/test-multi-export.php 的截图场景同一套口径),
|
||||
* 不是真实导出结果,仅用于看版式。
|
||||
*
|
||||
* 运行:php tools/make-samples.php
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
require __DIR__ . '/../bootstrap.php';
|
||||
require __DIR__ . '/../lib/multi_export.php';
|
||||
|
||||
/** 构造一名学生的若干次考试记录(成绩按时间升序) */
|
||||
function sample_stu(string $sid, string $name, float $best, array $scoresAsc, array $tempMarks = []): array
|
||||
{
|
||||
$list = [];
|
||||
$t = 1000000;
|
||||
foreach ($scoresAsc as $i => $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;
|
||||
Reference in New Issue
Block a user