feat(export/dashboard): 导出增强与大屏公开化
导出-每次考试/多考试对比: - 合格次数阈值等标记项抽为常显区块,三类成绩表共用 - 附缺考名单扩展到多考试导出(取所选考试全都没交的人) - 块表每场固定2列(成绩+合并空位),修复漏推空串导致的整行左移/串列 - 列宽按实际内容估算,合并区空位只给最小宽度 - 合格次数始终显示实际次数(含0),仅 ≥ 阈值标绿底 - 打印标题改为「成绩明细」,缺考名单分页单独成页 - 打印样式加 print-color-adjust:exact,绿底/红字可正常印出 数据大屏: - ?class= 班级大屏免登录公开访问,接口侧放行并按班级裁剪后回传 - 单班大屏移除退出按钮,仅首页保留 - 修复红绿判定真bug:membersTable 误用 window.DASH(DASH为let声明取不到) 改为 scoreGrade() 直接读自由变量,按后台合格率正确判红/黄/绿 测试/样例/文档同步更新,全量回归通过。
This commit is contained in:
+20
-5
@@ -11,9 +11,12 @@ pick() {
|
||||
if [ -n "$cand" ] && [ -x "$cand" ]; then echo "$cand"; return; fi
|
||||
command -v "$fallback" 2>/dev/null || echo "$fallback"
|
||||
}
|
||||
PHP="$(pick "${PHP_BIN:-C:/Users/Zhang/.workbuddy/binaries/php/php.exe}" php)"
|
||||
# 本项目用 PHP 7.4+ 语法(箭头函数 / 类型化属性),必须是 PHP 8;
|
||||
# 工作区里已备好便携版 8.2.34(其 php.ini 已开启 pdo_sqlite / zip / mbstring),
|
||||
# 想换别的解释器就设 PHP_BIN=... 覆盖
|
||||
PHP="$(pick "${PHP_BIN:-C:/Users/Zhang/.workbuddy/binaries/php/8.2.34/php.exe}" php)"
|
||||
NODE="$(pick "${NODE_BIN:-}" node)"
|
||||
INI="-d extension_dir=C:/Users/Zhang/.workbuddy/binaries/php/ext -d extension=pdo_sqlite"
|
||||
INI="${PHP_INI:-}"
|
||||
echo "PHP = $PHP"
|
||||
echo "NODE = $NODE"
|
||||
|
||||
@@ -32,15 +35,27 @@ NY_DATA_TMP="$(pwd)/.ny-test-data"
|
||||
mkdir -p "$NY_DATA_TMP"
|
||||
# 说明:test-login-loop.js 会清空数据目录里的账密来复现"未配置账号"现场,
|
||||
# 即使不传 NY_DATA 它也会自己建临时目录,绝不回退到项目 data/。
|
||||
for f in tools/test-config-save.js tools/test-upload-guard.js tools/test-whitelist.js tools/test-login-loop.js; do
|
||||
# 这几段依赖 mock-server(dashboard-nav 自带名单,另 4 段需本机 data/roster.json)。
|
||||
# 逐段容错,避免一个坏掉的环境把后面的测试整段带走;失败照常记账,末尾统一汇总退出码。
|
||||
e2e_fail=0
|
||||
for f in tools/test-config-save.js tools/test-upload-guard.js tools/test-whitelist.js tools/test-login-loop.js tools/test-dashboard-nav.js; do
|
||||
echo ""
|
||||
echo ">>> $f (NY_DATA=$NY_DATA_TMP)"
|
||||
NY_DATA="$NY_DATA_TMP" "$NODE" "$f"
|
||||
if ! NY_DATA="$NY_DATA_TMP" "$NODE" "$f"; then e2e_fail=$((e2e_fail + 1)); fi
|
||||
done
|
||||
# test-draw-alloc.js 不读写连接配置,沿用项目 data/ 即可
|
||||
echo ""
|
||||
echo ">>> tools/test-draw-alloc.js"
|
||||
"$NODE" "tools/test-draw-alloc.js"
|
||||
"$NODE" "tools/test-draw-alloc.js" || e2e_fail=$((e2e_fail + 1))
|
||||
|
||||
# 多考试打印渲染(纯前端函数,不需要服务器):从 app.js 抽取 sheetToHtml 校验合并单元格换算
|
||||
echo ""
|
||||
echo ">>> tools/test-block-print.js"
|
||||
"$NODE" "tools/test-block-print.js" || e2e_fail=$((e2e_fail + 1))
|
||||
|
||||
echo ""
|
||||
if [ "$e2e_fail" -gt 0 ]; then
|
||||
echo "全部测试跑完,但有 $e2e_fail 个 Node 段失败(多为 mock-server 缺 data/roster.json 导致的环境问题)"
|
||||
exit 1
|
||||
fi
|
||||
echo "全部测试完成。"
|
||||
|
||||
+286
-1
@@ -34,7 +34,7 @@ function check(string $name, bool $cond, string $detail = ''): void
|
||||
function stuRow(string $sid, string $name, array $scoresAsc, array $tempMarks = [], ?float $best = null): array
|
||||
{
|
||||
$list = [];
|
||||
$t = 1_000_000;
|
||||
$t = 1000000; // 不用数字分隔符,保证 PHP 7.3+ 都能跑回归测试
|
||||
foreach ($scoresAsc as $i => $sc) {
|
||||
$t += 600;
|
||||
$list[] = [
|
||||
@@ -132,5 +132,290 @@ $r3 = ['sid' => 'X', 'name' => 'Y', 'score' => 77.0, 'best_score' => 77.0]; //
|
||||
check('无明细 latest 退回代表行=77', ny_exam_cell_score($r3, 'latest') === 77.0);
|
||||
check('无明细 highest=77', ny_exam_cell_score($r3, 'highest') === 77.0);
|
||||
|
||||
/* ---------------- 「每次考试」块表(两行表头 / 合并单元格 / 未考) ---------------- */
|
||||
/**
|
||||
* 行内容比较:数据行是「参差不齐」的(某场次数少就没有尾巴上的格子),
|
||||
* 短的一侧按空串补齐到等长再逐格比,避免因为"少几个空格子"误判。
|
||||
*/
|
||||
function rowEq(array $a, array $b): bool
|
||||
{
|
||||
$n = max(count($a), count($b));
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$x = isset($a[$i]) ? (string)$a[$i] : '';
|
||||
$y = isset($b[$i]) ? (string)$b[$i] : '';
|
||||
if ($x !== $y) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** merges 转成 "A2:A4" 形式,便于与期望值比较(不依赖生成顺序) */function mergeRefs(array $merges): array
|
||||
{
|
||||
$col = static function (int $n): string {
|
||||
$s = '';
|
||||
while ($n > 0) {
|
||||
$m = ($n - 1) % 26;
|
||||
$s = chr(65 + $m) . $s;
|
||||
$n = (int)(($n - $m) / 26);
|
||||
}
|
||||
return $s;
|
||||
};
|
||||
$out = [];
|
||||
foreach ($merges as $m) {
|
||||
$out[] = $col($m[1]) . $m[0] . ':' . $col($m[3]) . $m[2];
|
||||
}
|
||||
sort($out);
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 构造带班级 / 组别的学生行 */
|
||||
function stuRowCG(string $sid, string $name, string $cls, string $grp, array $scoresAsc, array $tempMarks = [], ?float $best = null): array
|
||||
{
|
||||
$r = stuRow($sid, $name, $scoresAsc, $tempMarks, $best);
|
||||
$r['cls'] = $cls;
|
||||
$r['grp'] = $grp;
|
||||
return $r;
|
||||
}
|
||||
|
||||
// 版式场景完全对齐需求截图:张三 4 次数学 / 2 次英语;李四只有英语(数学「未考」)
|
||||
$shotMath = ['name' => '数学', 'rows' => [
|
||||
stuRow('356001', '张三', [112.0, 100.0, 136.0, 127.0], [], 136.0),
|
||||
]];
|
||||
$shotEng = ['name' => '英语', 'rows' => [
|
||||
stuRow('356001', '张三', [112.0, 100.0], [], 112.0),
|
||||
stuRow('356002', '李四', [130.0, 129.0, 133.0], [], 133.0),
|
||||
]];
|
||||
|
||||
echo "== 每次考试块表:两行表头 / 共享第几次 / 未考(截图一版式) ==\n";
|
||||
$bs = ny_assemble_block_sheet(['m' => $shotMath, 'e' => $shotEng]);
|
||||
$rows = $bs['rows'];
|
||||
check('表头第 1 行:学号 / 姓名 / 第几次 / 考试名(各占 1 格 + 1 空格)',
|
||||
$rows[0] === ['学号', '姓名', '第几次', '数学', '', '英语', ''],
|
||||
json_encode($rows[0], JSON_UNESCAPED_UNICODE));
|
||||
check('表头第 2 行:只有「成绩」小标题',
|
||||
$rows[1] === ['', '', '', '成绩', '', '成绩', ''],
|
||||
json_encode($rows[1], JSON_UNESCAPED_UNICODE));
|
||||
check('header_rows = 2', $bs['header_rows'] === 2);
|
||||
/* 每场考试固定占 2 列:第 1 列成绩,第 2 列是「成绩列与相邻列合并」落下的空位。
|
||||
当初漏推这个空串,整行会左移一格,表现就是成绩串列、表头合并区间对不上。 */
|
||||
check('张三块首行:第几次=1,两场成绩(每场 2 列)',
|
||||
rowEq($rows[2], ['356001', '张三', '1', '112', '', '112', '']),
|
||||
json_encode($rows[2], JSON_UNESCAPED_UNICODE));
|
||||
check('每场考试固定 2 列(成绩 + 落位空列),行宽齐整',
|
||||
count($rows) === 9 && !array_filter(array_map('count', $rows), static fn($n) => $n !== 7),
|
||||
'列数序列 = ' . json_encode(array_map('count', $rows)));
|
||||
check('张三第 3 行:第几次=3,数学 136 / 英语空',
|
||||
rowEq($rows[4], ['', '', '3', '136', '']),
|
||||
json_encode($rows[4], JSON_UNESCAPED_UNICODE));
|
||||
check('张三第 4 行:第几次=4,英语留空',
|
||||
rowEq($rows[5], ['', '', '4', '127', '']),
|
||||
json_encode($rows[5], JSON_UNESCAPED_UNICODE));
|
||||
check('李四块首行:数学未考 + 英语 130',
|
||||
rowEq($rows[6], ['356002', '李四', '1', '未考', '', '130']),
|
||||
json_encode($rows[6], JSON_UNESCAPED_UNICODE));
|
||||
check('总行数 = 9(2 行表头 + 张三 4 + 李四 3)', count($rows) === 9, (string)count($rows));
|
||||
check('total_rows / students 统计正确', $bs['total_rows'] === 7 && $bs['students'] === 2,
|
||||
$bs['total_rows'] . '/' . $bs['students']);
|
||||
check('未启用合格行时 pass_row=false', $bs['pass_row'] === false, var_export($bs['pass_row'], true));
|
||||
|
||||
$refs = mergeRefs($bs['merges']);
|
||||
check('合并区间:表头 C 列纵向 + 考试名横向 + 学号姓名第几次纵向 + 未考跨 2 列',
|
||||
$refs === ['A1:A2', 'A3:A6', 'A7:A9', 'B1:B2', 'B3:B6', 'B7:B9', 'C1:C2', 'C3:C6', 'C7:C9', 'D1:E1', 'D7:E9', 'F1:G1'],
|
||||
json_encode($refs));
|
||||
/* 列宽够用就好:空落位列给 2.5,宽度让给真正显示成绩的列,
|
||||
否则一屏只放得下两场考试。 */
|
||||
check('列宽 = 学号/姓名/第几次 + 成绩 10 + 空落位 2.5',
|
||||
$bs['widths'] === [10, 9, 9, 10, 2.5, 10, 2.5], json_encode($bs['widths']));
|
||||
|
||||
echo "== 每次考试块表:最高分标红 / 合格次数标绿 ==\n";
|
||||
// 合格线 110:
|
||||
// 张三数学 [112,100,136,127] → 112/136/127 合格 = 3
|
||||
// 张三英语 [112,100] → 112 合格 = 1
|
||||
// 李四英语 [130,129,133] → 130/129/133 合格 = 3
|
||||
$bp = ny_assemble_block_sheet(['m' => $shotMath, 'e' => $shotEng], ['pass_line' => 110]);
|
||||
$pp = $bp['rows'];
|
||||
check('启用合格行后 header_rows 仍为 2', $bp['header_rows'] === 2);
|
||||
check('pass_row=true 且 pass_line 回传', $bp['pass_row'] === true && $bp['pass_line'] === 110.0,
|
||||
var_export($bp['pass_line'], true));
|
||||
// 张三块:4 行成绩 + 1 行合格次数(第 7 行)
|
||||
check('张三合格次数行:数学 3 次 / 英语 1 次',
|
||||
rowEq($pp[6], ['', '', '合格次数', '3', '', '1']),
|
||||
json_encode($pp[6], JSON_UNESCAPED_UNICODE));
|
||||
// 李四块:3 行成绩 + 1 行合格次数(第 11 行)
|
||||
check('李四合格次数行:数学未考留空 / 英语 3 次',
|
||||
rowEq($pp[10], ['', '', '合格次数', '', '', '3']),
|
||||
json_encode($pp[10], JSON_UNESCAPED_UNICODE));
|
||||
check('总行数 = 11(2 + (4+1) + (3+1))', count($pp) === 11, (string)count($pp));
|
||||
check('total_rows 含合格次数行 = 9', $bp['total_rows'] === 9, (string)$bp['total_rows']);
|
||||
|
||||
$pm = $bp['cell_styles'];
|
||||
// 张三数学最高分 136 在第 5 行第 4 列 → 红字
|
||||
check('张三数学最高分 136 标红', ($pm['5,4'] ?? '') === 'red', json_encode($pm));
|
||||
// 张三英语最高分 112 在第 3 行第 6 列 → 红字
|
||||
check('张三英语最高分 112 标红', ($pm['3,6'] ?? '') === 'red', json_encode($pm));
|
||||
// 张三合格次数(第 7 行):数学 3 达标绿底、英语 1 达标绿底
|
||||
check('张三数学合格次数达标绿底', ($pm['7,4'] ?? '') === 'green', json_encode($pm));
|
||||
check('张三英语合格次数达标绿底', ($pm['7,6'] ?? '') === 'green', json_encode($pm));
|
||||
// 李四:数学未考 → 不标绿;英语 3 次达标绿底
|
||||
check('李四数学未考不标绿', !isset($pm['11,4']), json_encode($pm));
|
||||
check('李四英语合格次数达标绿底', ($pm['11,6'] ?? '') === 'green', json_encode($pm));
|
||||
|
||||
// 未达标(阈值 4 次)→ 仍显示实际次数(张三数学 3 / 英语 1),但不标绿
|
||||
$bh = ny_assemble_block_sheet(['m' => $shotMath, 'e' => $shotEng], ['pass_line' => 110, 'pass_times' => 4]);
|
||||
$hp = $bh['rows'];
|
||||
check('阈值 4 次时张三仍显示实际合格次数(不隐藏)',
|
||||
rowEq($hp[6], ['', '', '合格次数', '3', '', '1']),
|
||||
json_encode($hp[6], JSON_UNESCAPED_UNICODE));
|
||||
check('阈值 4 次时不达标不标绿',
|
||||
!isset($bh['cell_styles']['7,4']) && !isset($bh['cell_styles']['7,6']),
|
||||
json_encode($bh['cell_styles']));
|
||||
|
||||
echo "== 每次考试块表:草稿 / 多班级组别列 / 空考试 ==\n";
|
||||
// 赵六:数学第 2 次是草稿 → 默认跳过(不占编号)
|
||||
$draftMath = ['name' => '数学', 'rows' => [
|
||||
stuRow('D1', '赵六', [60.0, 99.0], [false, true], 99.0),
|
||||
]];
|
||||
$ds = ny_assemble_block_sheet(['m' => $draftMath]);
|
||||
check('默认跳过草稿:只剩 1 次',
|
||||
rowEq($ds['rows'][2], ['D1', '赵六', '1', '60']),
|
||||
json_encode($ds['rows'][2], JSON_UNESCAPED_UNICODE));
|
||||
check('跳过草稿后无多余行', count($ds['rows']) === 3, (string)count($ds['rows']));
|
||||
|
||||
$di = ny_assemble_block_sheet(['m' => $draftMath], ['skip_temp' => false]);
|
||||
check('保留草稿:2 次且草稿分参与',
|
||||
rowEq($di['rows'][3], ['', '', '2', '99']),
|
||||
json_encode($di['rows'][3], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 只有草稿的学生:跳过草稿后整场无有效成绩 → 不出现在表里
|
||||
$onlyDraft = ['name' => '数学', 'rows' => [
|
||||
stuRow('X1', '只交草稿', [88.0], [true], 88.0),
|
||||
stuRow('X2', '正常', [70.0], [], 70.0),
|
||||
]];
|
||||
$od = ny_assemble_block_sheet(['m' => $onlyDraft]);
|
||||
check('全草稿学生被剔除(只剩正常那位)', $od['students'] === 1, (string)$od['students']);
|
||||
check('全草稿学生未写「未考」占位', rowEq($od['rows'][2], ['X2', '正常', '1', '70']),
|
||||
json_encode($od['rows'][2], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 多班级 / 多组别:按 班级 → 组别 → 学号 排序,连续相同班级 / 组别合并
|
||||
$multi = ['name' => '数学', 'rows' => [
|
||||
stuRowCG('S3', '王五', '1班', 'B', [60.0]),
|
||||
stuRowCG('S4', '赵六', '2班', 'B', [50.0]),
|
||||
stuRowCG('S1', '张三', '1班', 'A', [90.0, 80.0]),
|
||||
stuRowCG('S2', '李四', '1班', 'A', [70.0]),
|
||||
]];
|
||||
$mc = ny_assemble_block_sheet(['m' => $multi], ['with_cls' => true, 'with_grp' => true]);
|
||||
check('多班级表头 = 班级/组别/学号/姓名/第几次/考试名',
|
||||
$mc['rows'][0] === ['班级', '组别', '学号', '姓名', '第几次', '数学', ''],
|
||||
json_encode($mc['rows'][0], JSON_UNESCAPED_UNICODE));
|
||||
check('按 班级 → 组别 → 学号 排序',
|
||||
[$mc['rows'][2][2], $mc['rows'][4][2], $mc['rows'][5][2], $mc['rows'][6][2]]
|
||||
=== ['S1', 'S2', 'S3', 'S4'],
|
||||
json_encode([$mc['rows'][2][2], $mc['rows'][4][2], $mc['rows'][5][2], $mc['rows'][6][2]]));
|
||||
check('班级 / 组别只写在学生块首行',
|
||||
rowEq($mc['rows'][2], ['1班', 'A', 'S1', '张三', '1', '90']),
|
||||
json_encode($mc['rows'][2], JSON_UNESCAPED_UNICODE));
|
||||
$mcRefs = mergeRefs($mc['merges']);
|
||||
check('相同班级 / 相同组别的单元格合并(1班 S1~S3 归一格,A 组 S1~S2 归一格)',
|
||||
in_array('A3:A6', $mcRefs, true) && in_array('B3:B5', $mcRefs, true),
|
||||
json_encode($mcRefs));
|
||||
check('班级列位移后学号 / 姓名合并列正确(C / D 列)',
|
||||
in_array('C3:C4', $mcRefs, true) && in_array('D3:D4', $mcRefs, true),
|
||||
json_encode($mcRefs));
|
||||
|
||||
// 空考试:只出两行表头
|
||||
$bEmpty = ny_assemble_block_sheet(['none' => ['name' => '无人考试', 'rows' => []]]);
|
||||
check('空考试只有两行表头', count($bEmpty['rows']) === 2 && $bEmpty['students'] === 0);
|
||||
check('空考试仅剩表头合并',
|
||||
mergeRefs($bEmpty['merges']) === ['A1:A2', 'B1:B2', 'C1:C2', 'D1:E1'],
|
||||
json_encode(mergeRefs($bEmpty['merges'])));
|
||||
|
||||
// 考试列顺序跟随传入顺序
|
||||
$bOrder = ny_assemble_block_sheet(['e' => $shotEng, 'm' => $shotMath]);
|
||||
check('考试列顺序跟随传入顺序(英语在前)',
|
||||
$bOrder['rows'][0][3] === '英语' && $bOrder['rows'][0][5] === '数学',
|
||||
json_encode($bOrder['rows'][0], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
echo "== 单场「每次考试」宽表(截图二版式) ==\n";
|
||||
$w = ny_assemble_attempt_wide_sheet($shotEng, ['pass_line' => 110]);
|
||||
check('表头 = 学号/姓名/第1次…第N次/合格次数',
|
||||
$w['rows'][0] === ['学号', '姓名', '第1次', '第2次', '第3次', '合格次数'],
|
||||
json_encode($w['rows'][0], JSON_UNESCAPED_UNICODE));
|
||||
check('每人一行,共 2 人', count($w['rows']) === 3 && $w['students'] === 2, (string)count($w['rows']));
|
||||
// 张三英语 [112,100]:只有 2 次,第 3 列留空;≥110 → 112 合格 = 1
|
||||
check('张三行:112 / 100 / 空,合格 1 次',
|
||||
$w['rows'][1] === ['356001', '张三', '112', '100', '', '1'],
|
||||
json_encode($w['rows'][1], JSON_UNESCAPED_UNICODE));
|
||||
// 李四英语 [130,129,133]:全 ≥110 = 3 次合格
|
||||
check('李四行:130 / 129 / 133,合格 3 次',
|
||||
$w['rows'][2] === ['356002', '李四', '130', '129', '133', '3'],
|
||||
json_encode($w['rows'][2], JSON_UNESCAPED_UNICODE));
|
||||
check('attempt_cols=3', $w['attempt_cols'] === 3, (string)$w['attempt_cols']);
|
||||
check('最高分 112(张三第 3 列)标红', ($w['cell_styles']['2,3'] ?? '') === 'red', json_encode($w['cell_styles']));
|
||||
check('最高分 133(李四第 5 列)标红', ($w['cell_styles']['3,5'] ?? '') === 'red', json_encode($w['cell_styles']));
|
||||
check('张三合格次数 1 次达标绿底', ($w['cell_styles']['2,6'] ?? '') === 'green', json_encode($w['cell_styles']));
|
||||
check('李四合格次数 3 次达标绿底', ($w['cell_styles']['3,6'] ?? '') === 'green', json_encode($w['cell_styles']));
|
||||
|
||||
// 阈值 4 次 → 都不到阈值,仍显示实际次数(1 / 3),但不标绿
|
||||
$w4 = ny_assemble_attempt_wide_sheet($shotEng, ['pass_line' => 110, 'pass_times' => 4]);
|
||||
check('阈值 4 次时仍显示实际合格次数(不隐藏)',
|
||||
$w4['rows'][1][5] === '1' && $w4['rows'][2][5] === '3',
|
||||
json_encode([$w4['rows'][1][5], $w4['rows'][2][5]], JSON_UNESCAPED_UNICODE));
|
||||
check('阈值 4 次时不标绿',
|
||||
!isset($w4['cell_styles']['2,6']) && !isset($w4['cell_styles']['3,6']),
|
||||
json_encode($w4['cell_styles']));
|
||||
|
||||
// 关闭标红 / 关掉合格行
|
||||
$wOff = ny_assemble_attempt_wide_sheet($shotEng, ['pass_line' => 110, 'mark_best' => false, 'pass_row' => false]);
|
||||
check('mark_best=false 不标红', $wOff['cell_styles'] === [], json_encode($wOff['cell_styles']));
|
||||
check('pass_row=false 表头无合格次数', !in_array('合格次数', $wOff['rows'][0], true),
|
||||
json_encode($wOff['rows'][0], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
/** 检查合并区间两两不重叠(区间重叠会让 Excel 打开时报"需要修复") */
|
||||
function overlapsOf(array $merges): array
|
||||
{
|
||||
$bad = [];
|
||||
$n = count($merges);
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
for ($j = $i + 1; $j < $n; $j++) {
|
||||
$a = $merges[$i];
|
||||
$b = $merges[$j];
|
||||
if ($a[0] <= $b[2] && $b[0] <= $a[2] && $a[1] <= $b[3] && $b[1] <= $a[3]) {
|
||||
$bad[] = json_encode($a) . '×' . json_encode($b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $bad;
|
||||
}
|
||||
|
||||
echo "== 班级 / 组别列的显示规则 ==\n";
|
||||
$f1 = ny_multi_col_flags(['1班'], [], ['1班'], ['A', 'B']);
|
||||
check('只选 1 个班级且未选组别 → 班级 / 组别列都不出',
|
||||
$f1['with_cls'] === false && $f1['with_grp'] === false, json_encode($f1));
|
||||
$f2 = ny_multi_col_flags(['1班', '2班'], ['A', 'B'], ['1班', '2班'], ['A', 'B']);
|
||||
check('选了多班级 + 多组别 → 两列都出',
|
||||
$f2['with_cls'] === true && $f2['with_grp'] === true, json_encode($f2));
|
||||
$f3 = ny_multi_col_flags(['1班'], ['A', 'B'], ['1班'], ['A', 'B']);
|
||||
check('只选 1 个班级但选了多组别 → 只出组别列',
|
||||
$f3['with_cls'] === false && $f3['with_grp'] === true, json_encode($f3));
|
||||
$f4 = ny_multi_col_flags([], [], ['1班'], ['A']);
|
||||
check('未筛选且数据只有 1 个班 / 1 个组 → 两列都不出',
|
||||
$f4['with_cls'] === false && $f4['with_grp'] === false, json_encode($f4));
|
||||
$f5 = ny_multi_col_flags([], [], ['1班', '2班', '3班'], ['A', 'B']);
|
||||
check('未筛选且数据跨 3 个班 → 按数据补出班级 / 组别列',
|
||||
$f5['with_cls'] === true && $f5['with_grp'] === true, json_encode($f5));
|
||||
$f6 = ny_multi_col_flags(['1班', '2班'], ['A'], ['1班', '2班'], ['A']);
|
||||
check('多班级 + 只选 1 个组别 → 只出班级列',
|
||||
$f6['with_cls'] === true && $f6['with_grp'] === false, json_encode($f6));
|
||||
|
||||
echo "== 合并区间不重叠(防 Excel 报修复) ==\n";
|
||||
check('截图版式:无重叠', overlapsOf($bs['merges']) === [], json_encode(overlapsOf($bs['merges'])));
|
||||
check('多班级多组别:无重叠', overlapsOf($mc['merges']) === [], json_encode(overlapsOf($mc['merges'])));
|
||||
check('单场导出(带班级组别列):无重叠',
|
||||
overlapsOf(ny_assemble_block_sheet(['m' => $multi], ['with_cls' => true])['merges']) === []);
|
||||
check('草稿模式:无重叠',
|
||||
overlapsOf(ny_assemble_block_sheet(['m' => $draftMath], ['skip_temp' => false])['merges']) === []);
|
||||
|
||||
echo "\n通过 {$pass} 项,失败 {$fail} 项\n";
|
||||
exit($fail === 0 ? 0 : 1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user