Files
nxxy335 d05fd4a9b5 feat(export/dashboard): 导出增强与大屏公开化
导出-每次考试/多考试对比:
- 合格次数阈值等标记项抽为常显区块,三类成绩表共用
- 附缺考名单扩展到多考试导出(取所选考试全都没交的人)
- 块表每场固定2列(成绩+合并空位),修复漏推空串导致的整行左移/串列
- 列宽按实际内容估算,合并区空位只给最小宽度
- 合格次数始终显示实际次数(含0),仅 ≥ 阈值标绿底
- 打印标题改为「成绩明细」,缺考名单分页单独成页
- 打印样式加 print-color-adjust:exact,绿底/红字可正常印出

数据大屏:
- ?class= 班级大屏免登录公开访问,接口侧放行并按班级裁剪后回传
- 单班大屏移除退出按钮,仅首页保留
- 修复红绿判定真bug:membersTable 误用 window.DASH(DASH为let声明取不到)
  改为 scoreGrade() 直接读自由变量,按后台合格率正确判红/黄/绿

测试/样例/文档同步更新,全量回归通过。
2026-09-27 22:04:17 +08:00

888 lines
39 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
--bg: #f4f6fa;
--panel: #ffffff;
--border: #e4e8f0;
--border-2: #eef1f6;
--text: #1d2433;
--text-2: #5b6577;
--text-3: #8b95a7;
/* 主色阶:--primary 基准、--primary-2 悬停/激活深一档、--primary-3 浅色渐变端 */
--primary: #2f6bff;
--primary-2: #2559db;
--primary-3: #5b8bff;
--primary-weak: #eaf0ff;
--brand-2: #6a4bff; /* 品牌渐变收尾(紫)—— logo/面板条/登录条 */
--ok: #12a86a;
--ok-2: #3ec99a; /* 成功渐变亮端(toast) */
--ok-weak: #e7f7ef;
--warn: #e08c1a;
--warn-2: #f0a54a; /* 警告渐变亮端(toast) */
--warn-weak: #fdf3e2;
--danger: #e0483d;
--danger-2: #ef6c64; /* 危险渐变亮端(toast) */
--danger-weak: #fdecea;
--ink: #1b2440; /* 登录页深底 */
--shadow: 0 1px 2px rgba(20, 30, 60, .05), 0 8px 24px rgba(20, 30, 60, .06);
--shadow-lg: 0 12px 32px rgba(20, 30, 60, .12);
--radius: 12px;
--radius-lg: 18px;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font: 14px/1.55 "PingFang SC", "Microsoft YaHei", "Segoe UI", system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
/* 顶部柔和渐变,增强页面层次 */
body {
background-image: linear-gradient(180deg, rgba(47, 107, 255, .06), rgba(47, 107, 255, 0) 300px);
background-repeat: no-repeat;
background-attachment: fixed;
}
/* ---------- 顶栏 ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 30;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 24px;
background: rgba(255, 255, 255, .92);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border);
box-shadow: 0 2px 12px rgba(20, 30, 60, .05);
}
.brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* 导航栏主标题(已移除 Logo 与副标题,只保留单一主标题) */
.brand-title {
margin: 0; font-size: 16px; font-weight: 700; letter-spacing: .3px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.logo {
width: 38px; height: 38px;
border-radius: 10px;
background: linear-gradient(135deg, #2f6bff, #6a4bff);
color: #fff; font-weight: 800; font-size: 15px;
display: grid; place-items: center;
letter-spacing: .5px;
}
.brand-text { display: flex; flex-direction: column; }
.brand-text strong { font-size: 16px; letter-spacing: .3px; }
.brand-text small { color: var(--text-3); font-size: 12px; }
.top-right { display: flex; align-items: center; gap: 10px; }
.conn {
display: inline-flex; align-items: center; gap: 7px;
padding: 5px 12px; border-radius: 999px;
font-size: 12px; font-weight: 600;
background: var(--border-2); color: var(--text-2);
}
.conn i { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); }
.conn-on { background: var(--ok-weak); color: var(--ok); }
.conn-on i { background: var(--ok); box-shadow: 0 0 0 3px rgba(18,168,106,.15); }
.conn-off { background: var(--border-2); color: var(--text-2); }
.conn-err { background: var(--danger-weak); color: var(--danger); }
.conn-err i { background: var(--danger); }
.conn-load i { background: var(--warn); animation: blink 1s infinite; }
.conn-warn { background: var(--warn-weak, #fff4e0); color: var(--warn, #e08c1a); }
.conn-warn i { background: var(--warn, #e08c1a); animation: blink 1s infinite; }
@keyframes blink { 50% { opacity: .25; } }
/* ---------- 按钮 ---------- */
.btn {
border: 1px solid var(--border);
background: #fff;
color: var(--text);
padding: 7px 14px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
transition: .15s;
font-family: inherit;
}
.btn:hover { border-color: #c9d2e3; background: #fafbfe; }
.btn:active { transform: translateY(1px); }
.btn:disabled,
.btn[disabled] {
opacity: .55;
cursor: not-allowed;
background: #eef1f6;
color: #a0aab8;
border-color: #d5dde8;
transform: none;
box-shadow: none;
}
.btn-primary:disabled,
.btn-primary[disabled] {
background: #c2c8d4;
border-color: #c2c8d4;
color: #fff;
}
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-2); border-color: var(--primary-2); }
.btn.sm { padding: 5px 10px; font-size: 12px; }
.btn.xs { padding: 3px 8px; font-size: 11px; }
.btn.danger { color: var(--danger); border-color: #f3cfcb; }
.btn.danger:hover { background: var(--danger-weak); }
.btn b {
display: inline-block; margin-left: 4px; padding: 0 6px;
background: var(--primary-weak); color: var(--primary);
border-radius: 999px; font-size: 11px;
}
/* 链接当按钮用(如「数据看板」):继承按钮外观,避免被浏览器弹窗拦截 */
a.btn {
display: inline-flex; align-items: center; justify-content: center;
text-decoration: none; white-space: nowrap;
}
a.btn:hover { text-decoration: none; }
/* ---------- 布局 ---------- */
.wrap { max-width: 1460px; margin: 0 auto; padding: 22px 26px 52px; }
.toolbar {
display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px 18px;
background: linear-gradient(180deg, #ffffff, #fbfcff);
border: 1px solid var(--border);
border-radius: var(--radius); padding: 16px 18px; box-shadow: var(--shadow);
}
.field { display: flex; flex-direction: column; gap: 7px; min-width: 150px; }
.field.grow { flex: 1 1 280px; min-width: 220px; }
.field.grow-2 { flex: 1 1 210px; min-width: 180px; }
.field.btns { flex-direction: row; gap: 8px; align-items: center; }
.field label { font-size: 12px; color: var(--text-2); font-weight: 600; letter-spacing: .3px; }
.inline { display: flex; align-items: center; gap: 10px; }
select, input[type=text], input[type=number], input[type=password] {
height: 36px; padding: 0 10px;
border: 1px solid var(--border); border-radius: 8px;
background: #fff; color: var(--text); font-size: 13px;
font-family: inherit; outline: none; width: 100%;
}
select:focus, input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(47,107,255,.12); }
.field.btns select { width: 100px; }
/* ---------- 副工具栏:导出等次要操作独立成行,避免挤在主工具栏里 ---------- */
.subbar {
display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
margin-top: 10px; padding: 10px 14px;
background: var(--panel); border: 1px solid var(--border);
border-radius: var(--radius); box-shadow: var(--shadow);
}
.subbar-label {
font-size: 12px; font-weight: 700; color: var(--text-3); letter-spacing: .5px;
padding-right: 12px; border-right: 1px solid var(--border);
}
.subbar-item {
display: inline-flex; align-items: center; gap: 6px;
font-size: 12px; color: var(--text-2); cursor: pointer; white-space: nowrap;
}
.subbar-item input[type=checkbox] { width: auto; height: auto; margin: 0; cursor: pointer; }
.subbar-item select { width: auto; min-width: 112px; height: 30px; padding: 0 8px; font-size: 12px; }
.switch { position: relative; display: inline-block; width: 40px; height: 22px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch span {
position: absolute; inset: 0; cursor: pointer;
background: #d3d9e6; border-radius: 999px; transition: .2s;
}
.switch span::before {
content: ''; position: absolute; width: 18px; height: 18px;
left: 2px; top: 2px; background: #fff; border-radius: 50%; transition: .2s;
}
.switch input:checked + span { background: var(--primary); }
.switch input:checked + span::before { transform: translateX(18px); }
/* ---------- 提示 ---------- */
.alert {
margin-top: 14px; padding: 12px 16px; border-radius: 10px;
background: var(--danger-weak); color: #a3342c; border: 1px solid #f5d3cf;
font-size: 13px; white-space: pre-wrap;
}
.alert.info { background: var(--primary-weak); color: #1a45ad; border-color: #ccdaff; }
.hidden { display: none !important; }
/* ---------- 卡片 ---------- */
.cards {
display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 14px; margin-top: 16px;
}
.card {
position: relative; overflow: hidden;
background: var(--panel); border: 1px solid var(--border);
border-radius: var(--radius); padding: 16px 18px; box-shadow: var(--shadow);
display: flex; flex-direction: column; gap: 6px;
transition: transform .18s ease, box-shadow .18s ease;
}
.card::before {
content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
background: var(--accent, var(--primary));
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.card span { font-size: 12px; color: var(--text-3); }
.card b { font-size: 25px; font-weight: 700; letter-spacing: .5px; white-space: nowrap; }
.card.ok b { color: var(--ok); }
.card.warn b { color: var(--warn); }
.card.accent-blue { --accent: #2f6bff; }
.card.accent-green { --accent: #12a86a; }
.card.accent-orange { --accent: #e08c1a; }
.card.accent-purple { --accent: #8b5cf6; }
.card.accent-teal { --accent: #0ea5a4; }
.card.accent-red { --accent: #e0483d; }
.card.accent-indigo { --accent: #4f6bed; }
.card.accent-gray { --accent: #94a3b8; }
/* ---------- 面板 ---------- */
.panel {
margin-top: 18px; background: var(--panel);
border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: var(--shadow); overflow: hidden;
}
/* 在 .layout 容器内由 gap 控制间距,去掉 panel 自身 margin-top */
.layout > .panel { margin-top: 0; }
.panel-head {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 18px; border-bottom: 1px solid var(--border-2);
background: linear-gradient(180deg, #ffffff, #fbfcfe);
}
.panel-head h2 {
margin: 0; font-size: 15px; font-weight: 700;
display: flex; align-items: center; gap: 9px;
}
.panel-head h2::before {
content: ''; flex: none; width: 4px; height: 16px; border-radius: 3px;
background: linear-gradient(180deg, var(--primary), #6a4bff);
}
.panel-head h2 small { font-weight: 500; color: var(--text-3); font-size: 12px; margin-left: 0; }
.updated { font-size: 12px; color: var(--text-3); display: flex; align-items: center; gap: 6px; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: #cbd3e2; display: inline-block; }
.dot.on { background: var(--ok); animation: pulse 1.6s infinite; }
.dot.stale { background: var(--danger); animation: blink 1s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
/* ---------- 三 panel 布局容器(默认 / 成绩明细与缺考名单同行) ---------- */
.layout {
display: grid; gap: 18px;
grid-template-columns: 1fr;
margin-top: 18px;
}
.layout > #tablePanel { grid-column: 1; grid-row: 1; }
.layout > #absentPanel { grid-column: 1; grid-row: 2; }
.layout > #unmatchedPanel { grid-column: 1; grid-row: 3; }
/* side:成绩明细 与 缺考名单 同行,未匹配答卷放下面整宽 */
.layout[data-layout="side"] {
grid-template-columns: 1fr 1fr;
}
.layout[data-layout="side"] > #tablePanel { grid-column: 1; grid-row: 1; }
.layout[data-layout="side"] > #absentPanel { grid-column: 2; grid-row: 1; }
.layout[data-layout="side"] > #unmatchedPanel { grid-column: 1 / -1; grid-row: 2; }
/* 窄屏一律回落单列(grid-row 也要还原) */
@media (max-width: 900px) {
.layout, .layout[data-layout="side"] { grid-template-columns: 1fr; }
.layout > #tablePanel, .layout > #absentPanel, .layout > #unmatchedPanel,
.layout[data-layout="side"] > #tablePanel,
.layout[data-layout="side"] > #absentPanel,
.layout[data-layout="side"] > #unmatchedPanel {
grid-column: 1;
}
.layout > #tablePanel, .layout[data-layout="side"] > #tablePanel { grid-row: 1; }
.layout > #absentPanel, .layout[data-layout="side"] > #absentPanel { grid-row: 2; }
.layout > #unmatchedPanel, .layout[data-layout="side"] > #unmatchedPanel { grid-row: 3; }
}
/* ---------- 表格 ---------- */
.table-scroll { overflow: auto; max-height: 62vh; scrollbar-gutter: stable; }
.table-scroll.short { max-height: 320px; }
.table-scroll.mid { max-height: 380px; }
table.grid { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; table-layout: fixed; }
table.grid th {
position: sticky; top: 0; z-index: 2;
background: linear-gradient(180deg, #fbfcfe, #f4f7fc); color: var(--text-2);
text-align: left; font-weight: 600; font-size: 12px;
padding: 11px 14px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
/* 用 box-shadow 画分隔线而非 border:border 在 border-collapse:separate + sticky 下
不会与下方 td 边框产生折叠重绘缝隙(消除吸顶表头在滚动边界的 1px 双线/错位观感) */
background-clip: padding-box;
box-shadow: inset 0 -1px 0 var(--border);
}
table.grid th.sortable { cursor: pointer; user-select: none; }
table.grid th.sortable:hover { color: var(--primary); }
table.grid th .arrow { color: var(--primary); margin-left: 3px; }
table.grid td { padding: 10px 14px; border-bottom: 1px solid var(--border-2); vertical-align: middle; background-clip: padding-box; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 含按钮的操作列:不能被 ellipsis 的 overflow:hidden 裁掉按钮 */
table.grid td.act { overflow: visible; text-overflow: clip; }
/* 虚拟滚动占位行:不占视觉空间、不响应 hover/条纹、不显示边框 */
table.grid tbody tr.vspacer td { padding: 0; border: 0; background: transparent !important; }
table.grid tbody tr.vspacer:nth-child(even) td { background: transparent !important; }
table.grid tbody tr.vspacer:hover td { background: transparent !important; }
table.grid tbody tr:nth-child(even) td { background: #fbfcff; }
table.grid tbody tr:hover td { background: var(--primary-weak); }
table.grid.compact td, table.grid.compact th { padding: 7px 12px; }
td.empty { text-align: center; color: var(--text-3); padding: 32px 0; }
td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
td.sid { font-weight: 600; color: var(--text-2); font-variant-numeric: tabular-nums; }
/* 数值列右对齐:成绩 / 次数 / 时长纵向扫读时更容易对齐比对 */
table.grid td.num, table.grid td.score { text-align: right; }
/* 表头对齐 = data-k 派生,与单元格的正则保持一致;避免"表头左对齐、数据右对齐"造成的视觉错列 */
table.grid thead th[data-k="score"],
table.grid thead th[data-k="attempts"],
table.grid thead th[data-k="submit_ts"],
table.grid thead th[data-k="duration"] { text-align: right; }
table.grid td.score { font-weight: 700; font-size: 14px; }
table.grid td.name { font-weight: 600; }
td.name { font-weight: 600; }
td.score { font-weight: 700; font-variant-numeric: tabular-nums; }
.score-high { color: var(--ok); }
.score-mid { color: var(--warn); }
.score-low { color: var(--danger); }
/* 新数据行动画:FLIP 滑动(行移动) + 全新行落入;尊重 prefers-reduced-motion */
@keyframes rowDrop { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
table.grid tbody tr.row-drop-in { animation: rowDrop .42s ease; }
@media (prefers-reduced-motion: reduce) {
table.grid tbody tr.row-drop-in { animation: none; }
}
/* ---------- Toast 顶部飘窗通知 ---------- */
.toast {
background: linear-gradient(135deg, var(--primary), var(--primary-3));
color: #fff;
}
.toast-ok { background: linear-gradient(135deg, var(--ok), var(--ok-2)); color: #fff; }
.toast-error{ background: linear-gradient(135deg, var(--danger), var(--danger-2)); color: #fff; }
.toast-warn { background: linear-gradient(135deg, var(--warn), var(--warn-2)); color: #fff; }
@keyframes toastIn {
from { opacity: 0; transform: translateY(-12px) scale(.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes toastOut {
from { opacity: 1; transform: translateY(0) scale(1); }
to { opacity: 0; transform: translateY(-8px) scale(.96); }
}
/* 新数据绿色底纹:常亮(不再闪烁),由"清除高亮"或切换项目/姓名题清除 */
table.grid tbody tr.new td { background: #e7f7ee; }
table.grid tbody tr.new:nth-child(even) td { background: #e7f7ee; }
table.grid tbody tr.new:hover td { background: #d6f2e2; }
/* 左侧色条,进一步强调"这条是新的" */
table.grid tbody tr.new td:first-child { box-shadow: inset 3px 0 0 var(--ok); }
/* 考试次数列 */
td.att { font-weight: 700; color: var(--text-2); }
.tag {
display: inline-block; padding: 1px 8px; border-radius: 999px;
font-size: 11px; font-weight: 600;
}
.tag-ok { background: var(--ok-weak); color: var(--ok); }
.tag-warn { background: var(--warn-weak); color: var(--warn); }
.tag-gray { background: var(--border-2); color: var(--text-3); }
.tag-att { background: #e8eefc; color: #3457c9; cursor: pointer; }
.tag-new { background: var(--ok-weak); color: var(--ok); }
.tag-best { background: #fff1dc; color: #b26a06; }
/* 当日最高(历史成绩按天分组后,组内最高分) */
.tag-day { background: var(--primary-weak); color: var(--primary-2); }
/* 历史成绩弹窗:按提交日期分组的分组行 */
tr.att-day > td { background: var(--border-2); font-weight: 600; color: var(--text-2); }
tr.att-day > td .att-day-sub { margin-left: 8px; font-weight: 400; font-size: 12px; color: var(--text-3); }
/* 多次刷题:成员榜可点击行 */
tr.clickable { cursor: pointer; }
tr.clickable:hover td { background: var(--primary-weak); }
/* 绿色常亮行 hover 时保持绿色调,避免看起来"消失" */
table.grid tbody tr.new.clickable:hover td { background: #d6f2e2; }
.muted { color: var(--text-3); }
/* ---------- 弹窗 ---------- */
.modal {
position: fixed; inset: 0; z-index: 100;
background: rgba(20, 28, 48, .45);
display: none; align-items: center; justify-content: center;
padding: 24px; backdrop-filter: blur(2px);
}
.modal.open { display: flex; }
.modal-box {
background: #fff; border-radius: 14px; width: 640px; max-width: 100%;
max-height: 90vh; display: flex; flex-direction: column;
box-shadow: 0 24px 64px rgba(15, 23, 42, .28);
}
.modal-box.wide { width: 820px; }
.modal-head {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.modal-head h3 { margin: 0; font-size: 15px; }
.x { border: 0; background: none; font-size: 22px; line-height: 1; cursor: pointer; color: var(--text-3); }
.x:hover { color: var(--text); }
.modal-body { padding: 18px; overflow: auto; }
/* 弹窗内的表格不做吸顶:弹窗自身是滚动容器,吸顶表头会与内容错层(且贴边观感差) */
.modal-body table.grid th { position: static; }
.modal-body table.grid { box-shadow: inset 0 0 0 1px var(--border-2); border-radius: 8px; }
/* ================= 统一导出 / 打印弹窗 ================= */
.ex-sec {
padding: 12px 14px; margin: 0 0 12px;
border: 1px solid var(--border); border-radius: 10px; background: #fbfcfe;
}
.ex-sec[hidden] { display: none; }
.ex-sec-title {
font-size: 12px; font-weight: 700; color: var(--text-2);
letter-spacing: .3px; margin: 0 0 10px;
display: flex; align-items: center; gap: 8px;
}
.ex-sec-title small { font-weight: 500; color: var(--text-3, #8b93a1); }
/* 导出内容:2×2 卡片单选,一眼看清四种导出各是什么 */
.ex-types { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ex-type {
display: flex; align-items: flex-start; gap: 9px; padding: 10px 12px;
border: 1px solid var(--border); border-radius: 9px; background: #fff;
cursor: pointer; transition: border-color .15s, box-shadow .15s, background .15s;
}
.ex-type:hover { border-color: #b9c2d0; }
.ex-type:has(input:checked) {
border-color: var(--primary, #2f6fed); background: #f3f7ff;
box-shadow: 0 0 0 1px var(--primary, #2f6fed) inset;
}
.ex-type input { margin-top: 3px; flex: none; }
.ex-type-t { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ex-type-t b { font-size: 13px; color: var(--text); }
.ex-type-t small { font-size: 11.5px; color: var(--text-3, #8b93a1); line-height: 1.45; }
/* 字段行 */
.ex-row { display: flex; flex-wrap: wrap; gap: 14px 18px; align-items: flex-start; }
.ex-field {
display: flex; flex-direction: column; gap: 6px; min-width: 150px;
font-size: 12px; font-weight: 600; color: var(--text-2);
}
.ex-field select, .ex-field input[type=text], .ex-field input[type=number] {
font-size: 13px; font-weight: 400; color: var(--text);
}
.ex-field-tip {
min-width: 200px; flex: 1 1 240px; font-weight: 400;
display: flex; flex-direction: column; gap: 4px; justify-content: flex-end;
}
.ex-field-grow { flex: 1 1 240px; }
.ex-field-grow input { width: 100%; }
/* 班级 / 组别多选:竖排标签 + 列表,便于 Ctrl 多选 */
#exClsSel, #exGrpSel {
width: 168px; height: 96px; padding: 4px 6px; font-size: 12px;
border: 1px solid var(--border); border-radius: 8px;
background: #fff; color: var(--text); outline: none;
}
#exGrpSel { width: 132px; }
.ex-checks {
display: flex; flex-wrap: wrap; gap: 8px 20px; margin-top: 12px;
font-size: 12.5px; color: var(--text);
}
.ex-checks label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
/* 多考试:勾选列表 */
.ex-pick { margin-top: 12px; }
.ex-pick-head {
display: flex; align-items: center; gap: 10px; margin: 0 0 8px;
font-size: 12.5px; font-weight: 600; color: var(--text-2);
}
.ex-pick-head small { font-weight: 500; color: var(--text-3, #8b93a1); }
.table-scroll.mid { max-height: 208px; overflow: auto; }
/* 合规的旧类名保留(其它地方仍在用 .me-bar 之类的排版习惯) */
.me-bar { display: flex; align-items: center; gap: 16px; margin: 0 0 10px; }
.me-bar .chk { margin: 0; white-space: nowrap; }
.modal-foot {
display: flex; align-items: center; gap: 8px;
padding: 12px 18px; border-top: 1px solid var(--border); background: #fbfcfe;
border-radius: 0 0 14px 14px;
}
.spacer { flex: 1; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.fi { display: flex; flex-direction: column; gap: 6px; }
.fi.full { grid-column: 1 / -1; }
.fi label { font-size: 12px; font-weight: 600; color: var(--text-2); }
.fi label .req { color: var(--danger, #e5484d); margin-left: 2px; }
.fi-inline { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.fi-inline .chk { font-weight: 500; font-size: 13px; color: var(--text); }
.hint { margin: 0; font-size: 11px; color: var(--text-3); }
.fi.checks { flex-direction: row; flex-wrap: wrap; gap: 18px; }
.fi.checks label { display: flex; align-items: center; gap: 6px; font-weight: 500; font-size: 13px; color: var(--text); }
.ping-result {
margin-top: 14px; padding: 10px 12px; border-radius: 8px;
font-size: 12px; white-space: pre-wrap;
background: var(--ok-weak); color: #0d7a4d; border: 1px solid #c8ecda;
}
.ping-result.err { background: var(--danger-weak); color: #a3342c; border-color: #f5d3cf; }
.ping-result.loading { background: var(--border-2); color: var(--text-2); border-color: var(--border-2); }
/* ---------- 名单 ---------- */
.upload-zone {
border: 2px dashed #cfd8ea; border-radius: 10px; padding: 22px;
text-align: center; cursor: pointer; transition: .15s; background: #fbfcfe;
}
.upload-zone:hover, .upload-zone.over { border-color: var(--primary); background: var(--primary-weak); }
.uz-inner { display: flex; flex-direction: column; gap: 4px; }
.uz-inner strong { font-size: 13px; }
.uz-inner span { font-size: 12px; color: var(--text-3); }
.roster-bar {
display: flex; align-items: center; gap: 10px; margin: 14px 0 10px; flex-wrap: wrap;
}
.roster-bar input[type=text] { width: 180px; }
.chk { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-2); }
#rosterBody input {
width: 100%; border: 1px solid transparent; background: transparent;
padding: 4px 6px; border-radius: 6px; font-size: 13px; font-family: inherit; color: var(--text);
}
#rosterBody input:focus { border-color: var(--primary); background: #fff; outline: none; }
.link-del { color: var(--danger); cursor: pointer; font-size: 12px; }
/* ---------- 登录门 ---------- */
.login-gate {
position: fixed; inset: 0; z-index: 200;
display: flex; align-items: center; justify-content: center;
padding: 24px; background: linear-gradient(135deg, #1b2440, #2f6bff);
overflow: hidden;
}
.login-gate::before,
.login-gate::after {
content: ''; position: absolute; border-radius: 50%;
background: rgba(255, 255, 255, .08); pointer-events: none;
}
.login-gate::before { width: 340px; height: 340px; top: -90px; right: -70px; }
.login-gate::after { width: 280px; height: 280px; bottom: -80px; left: -60px; background: rgba(255, 255, 255, .06); }
.login-gate.hidden { display: none; }
.login-box {
position: relative; overflow: hidden;
width: 420px; max-width: 100%;
background: #fff; border-radius: var(--radius-lg); padding: 30px 28px 28px;
box-shadow: 0 24px 64px rgba(15, 23, 42, .35);
}
.login-box::before {
content: ''; position: absolute; left: 0; right: 0; top: 0; height: 4px;
background: linear-gradient(90deg, #2f6bff, #6a4bff);
}
.login-brand { display: flex; align-items: center; gap: 12px; margin: 2px 0 8px; }
.login-brand .logo { width: 46px; height: 46px; border-radius: 13px; font-size: 18px; }
.login-tip { margin: 0 0 20px; font-size: 12px; color: var(--text-3); line-height: 1.6; }
.login-box .fi { margin-bottom: 14px; }
.login-actions { display: flex; gap: 10px; margin-top: 6px; }
.login-actions .btn { flex: 1; }
/* ---------- 全屏适配 ---------- */
body.fs-on .wrap { max-width: none; padding-top: 8px; }
body.fs-on .table-scroll { max-height: calc(100vh - 230px); }
body.fs-on .table-scroll.short { max-height: 40vh; }
body.fs-on .cards { gap: 14px; }
body.fs-on .card b { font-size: 28px; }
body.fs-on .topbar { padding-top: 8px; padding-bottom: 8px; }
/* ---------- 三端自适应(桌面 / 平板 / 手机) ---------- */
/* 大屏笔记本:略收边距,统计卡片保持一行 */
@media (max-width: 1180px) {
.wrap { padding: 16px 16px 40px; }
.topbar { padding: 10px 16px; }
}
/* 平板横屏(769~1024):卡片改 4 列、工具栏字段占满整行 */
@media (max-width: 1024px) {
.field.grow, .field.grow-2 { flex: 1 1 100%; min-width: 0; }
.cards { grid-template-columns: repeat(4, 1fr); }
.toolbar { gap: 14px; }
}
/* 平板竖屏 / 大手机(<=768):整体收紧,工具栏与按钮自动换行 */
@media (max-width: 768px) {
body { font-size: 13px; }
.topbar { flex-wrap: wrap; gap: 10px; }
.brand { flex: 1 1 100%; }
.brand-title { font-size: 15px; }
.top-right { flex-wrap: wrap; gap: 8px; }
.top-right .btn { padding: 6px 10px; font-size: 12px; }
.btn b { font-size: 10px; }
.wrap { padding: 12px 12px 36px; }
.toolbar { padding: 12px; gap: 10px 12px; }
.field { flex: 1 1 100%; min-width: 0; }
.field.btns { flex: 1 1 100%; flex-wrap: wrap; row-gap: 8px; }
.field.btns select { width: 100%; }
.cards { grid-template-columns: repeat(3, 1fr); gap: 8px; }
.card { padding: 10px 12px; }
.card b { font-size: 20px; }
.card span { font-size: 11px; }
/* 面板头部(含按钮)竖排,按钮自动换行 */
.panel-head { flex-direction: column; align-items: flex-start; gap: 8px; }
/* 竖排时 .spacer(flex:1)会把内容上下撑开,必须隐藏 */
.panel-head > .spacer { display: none; }
.panel-head .updated { flex-wrap: wrap; width: 100%; }
.panel-head .btn, .panel-head .updated .btn { flex: 0 0 auto; }
.table-scroll { max-height: 56vh; }
table.grid { font-size: 12px; }
table.grid th { padding: 8px 10px; white-space: nowrap; }
table.grid td { padding: 7px 10px; }
.modal { padding: 10px; }
.modal-box, .modal-box.wide { width: 100%; max-height: 92vh; }
.form-grid { grid-template-columns: 1fr; }
.modal-body { padding: 14px; }
/* 弹窗底栏在小屏换行,按钮可点满宽 */
.modal-foot { flex-wrap: wrap; padding: 10px 14px; }
.modal-foot > .spacer { display: none; }
.modal-foot .btn { flex: 1 1 auto; }
.roster-bar { flex-wrap: wrap; }
.roster-bar .chk { font-size: 11px; }
/* 名单表在窄屏可横向滚动,避免 6 列挤爆 */
#rosterTable { min-width: 560px; }
}
/* 手机(<=480):卡片两列,进一步压缩字号与内边距 */
@media (max-width: 480px) {
.cards { grid-template-columns: repeat(2, 1fr); gap: 8px; }
.brand-title { font-size: 14px; }
.btn { padding: 6px 9px; font-size: 12px; }
.field label { font-size: 11px; }
.roster-bar { gap: 8px; }
.roster-bar input[type=text] { width: 120px; }
.panel-head h2 { font-size: 14px; }
#rosterBody input { font-size: 12px; }
.modal-box { max-height: 94vh; }
.modal-body { padding: 12px; }
/* 工具条按钮在小屏铺满,便于点按 */
.field.btns .btn { flex: 1 1 auto; }
}
/* 触摸设备:增大点按热区,去掉 hover 残留高亮 */
@media (hover: none) and (pointer: coarse) {
.btn { min-height: 34px; }
table.grid tbody tr:hover td { background: inherit; }
}
/* ---------- 数据看板 ---------- */
.dash-hero {
background: linear-gradient(135deg, #1b2440, #2f6bff);
color: #fff; padding: 20px 24px; border-radius: var(--radius-lg);
display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
box-shadow: var(--shadow);
}
.dash-hero .brand-title { color: #fff; font-size: 17px; }
.dash-hero a.back { color: #fff; opacity: .9; font-size: 13px; text-decoration: none; font-weight: 600; }
.dash-hero a.back:hover { opacity: 1; text-decoration: underline; }
.dash-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dash-controls select { width: 230px; height: 38px; }
/* 看板顶栏的「主系统 / 退出」用描边区分于同排的半透明操作按钮 */
.dash-controls .btn.back {
background: transparent; border-color: rgba(255, 255, 255, .45); font-weight: 600;
}
.dash-controls .btn.back:hover { background: rgba(255, 255, 255, .18); border-color: #fff; }
/* 窄屏:下拉与按钮换行铺开,避免 230px 固定宽把顶栏撑出横向滚动条 */
@media (max-width: 860px) {
.dash-hero { padding: 14px 16px; }
.dash-controls { width: 100%; gap: 8px; }
.dash-controls select { width: auto; flex: 1 1 160px; min-width: 0; }
.dash-controls .btn { flex: 0 0 auto; }
}
.dash-controls .inline { color: #fff; }
.dash-controls .btn { background: rgba(255, 255, 255, .14); border-color: transparent; color: #fff; }
.dash-controls .btn:hover { background: rgba(255, 255, 255, .24); }
/* 单班大屏:顶栏只保留「试卷 / 自动刷新 / 全屏」,班级选择与「← 主系统」隐藏
(退出按钮本形态下不再渲染,这里不再靠 CSS 藏) */
body.dash-class #classSelWrap,
body.dash-class #linkIndex { display: none !important; }
/* 公开(免登录)班级大屏的角标 */
.pub-tag {
display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px;
background: rgba(255, 255, 255, .16); border: 1px solid rgba(255, 255, 255, .38); color: #fff;
}
/* 首页汇总卡(5 卡)在该形态下无意义 */
body.dash-home #boardSummary { display: none; }
.dash-hero .brand-cls {
display: inline-block; margin-left: 2px; padding: 2px 10px; border-radius: 999px;
background: rgba(255, 255, 255, .18); font-size: 13px; font-weight: 600;
}
/* ---------- 首页:班级入口卡片 ---------- */
.cls-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
gap: 14px; margin-top: 12px;
}
.cls-card {
min-width: 0; cursor: pointer; user-select: none;
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
padding: 14px 16px 12px; box-shadow: var(--shadow);
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.cls-card:hover { transform: translateY(-3px); border-color: var(--primary); box-shadow: var(--shadow-lg); }
.cls-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.cls-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.cls-name { font-size: 16px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cls-go { flex: none; font-size: 12px; font-weight: 700; color: var(--primary); }
.cls-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 8px; margin-top: 10px; }
.cls-kpi { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cls-kpi span { font-size: 11px; color: var(--text-3); }
.cls-kpi b { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cls-kpi.k-ok b { color: var(--ok); }
.cls-kpi.k-warn b { color: var(--warn); }
.cls-bar { height: 8px; border-radius: 999px; background: var(--border-2); overflow: hidden; margin-top: 12px; }
.cls-bar > i { display: block; height: 100%; border-radius: 999px; transition: width .5s ease; }
.cls-sub {
margin-top: 7px; font-size: 11.5px; color: var(--text-3);
display: flex; justify-content: space-between; gap: 8px;
}
@media (max-width: 860px) { .cls-grid { grid-template-columns: 1fr; } }
.sec-title { margin: 22px 0 4px; font-size: 15px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
.sec-title .tag-gray { background: var(--border-2); color: var(--text-3); padding: 1px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.bar { height: 9px; border-radius: 999px; background: var(--border-2); overflow: hidden; }
.bar > i { display: block; height: 100%; border-radius: 999px; transition: width .5s ease; }
.class-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; margin-top: 14px; }
.class-metrics .m { display: flex; flex-direction: column; gap: 2px; }
.class-metrics .m span { color: var(--text-3); font-size: 11px; }
.class-metrics .m b { font-size: 17px; font-weight: 700; }
.metric-ok b { color: var(--ok); }
.metric-danger b { color: var(--danger); }
.empty { text-align: center; color: var(--text-3); padding: 40px 0; }
/* 分组对比:A 在左、B 在右 */
.group-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 14px; }
@media (max-width: 860px) { .group-compare { grid-template-columns: 1fr; } }
.gpanel {
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
padding: 16px 18px; box-shadow: var(--shadow);
}
.gpanel .ghead { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; }
.gpanel .ghead strong { font-size: 16px; }
.gpanel .ghead .rate-num { font-size: 15px; font-weight: 800; }
/* 成员榜:排名 / 姓名 / 成绩 */
.mem-title {
margin-top: 16px; margin-bottom: 8px;
font-size: 12px; font-weight: 700; color: var(--text-2);
}
.member-list {
max-height: 300px; overflow: auto;
border: 1px solid var(--border-2); border-radius: 10px; background: #fff;
}
.member-list table.grid { font-size: 12.5px; }
.member-list table.grid th { padding: 8px 10px; }
.member-list table.grid td { padding: 6px 10px; }
.rk {
display: inline-flex; align-items: center; justify-content: center;
min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px;
background: var(--border-2); color: var(--text-2);
font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.rk-1 { background: #fff2d6; color: #b7791f; }
.rk-2 { background: #eef1f6; color: #64748b; }
.rk-3 { background: #fbe9dd; color: #b45309; }
/* 防溢出:网格子项允许收缩,长内容不撑破页面 */
.group-compare, .cards, .gpanel { min-width: 0; }
.gpanel { overflow: hidden; }
.member-list td.name { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 看板全屏:整页不溢出屏幕(外层锁 100vh + overflow:hidden,内部区域自行滚动) */
body.dash.fs-on .wrap {
height: 100vh; display: flex; flex-direction: column; overflow: hidden;
max-width: none; padding: 12px 20px 14px;
}
body.dash.fs-on .dash-hero { padding: 12px 18px; flex: 0 0 auto; border-radius: var(--radius); }
body.dash.fs-on .sec-title { margin: 12px 0 4px; font-size: 14px; }
body.dash.fs-on .cards { gap: 10px; margin-top: 10px; }
body.dash.fs-on .card { padding: 10px 12px; }
body.dash.fs-on .card b { font-size: 20px; }
body.dash.fs-on .group-compare { gap: 12px; }
body.dash.fs-on .gpanel { padding: 12px 14px; }
body.dash.fs-on .member-list { max-height: none; }
body.dash.fs-on .cls-grid { grid-template-columns: repeat(auto-fill, minmax(212px, 1fr)); gap: 10px; }
body.dash.fs-on .cls-card { padding: 10px 12px; }
body.dash.fs-on .cls-kpi b { font-size: 15px; }
/* ================= 随机抽取页 ================= */
.panel-head .actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 抽取人数输入框:在 toolbar 内联按钮组中收窄 */
.field .inline input[type=number] { width: 78px; flex: none; }
.draw-stage {
margin: 18px; padding: 26px 18px;
border: 1px dashed var(--border); border-radius: var(--radius);
background: linear-gradient(180deg, #fbfcff, #f6f8ff);
text-align: center; font-size: 20px; font-weight: 700;
color: var(--text-3); letter-spacing: 1px;
min-height: 86px; display: flex; align-items: center; justify-content: center;
transition: color .2s ease, background .2s ease;
}
.draw-stage.rolling {
color: var(--primary);
background: linear-gradient(180deg, #f2f6ff, #eaf0ff);
animation: drawShake .12s infinite alternate;
}
.draw-stage.done {
color: var(--ok);
background: linear-gradient(180deg, #f2fbf5, #e9f7ee);
font-size: 26px;
}
/* 只抽 1 人时的副行(学号 · 班级 · 组别),字号小于主名字 */
.draw-stage-sub {
margin-top: 8px; font-size: 14px; font-weight: 600;
color: var(--text-2); letter-spacing: 0;
}
@keyframes drawShake {
from { transform: translateY(-1px) scale(.995); }
to { transform: translateY(1px) scale(1.005); }
}
@media (prefers-reduced-motion: reduce) {
.draw-stage.rolling { animation: none; }
}
/* ================= side 布局:两 panel 等高(缺考名单不再留大片空白) ================= */
/* 宽屏 side 模式:panel 变纵向 flex,表格区撑满剩余高度(行高由内容多的一侧决定) */
@media (min-width: 901px) {
.layout[data-layout="side"] > #tablePanel,
.layout[data-layout="side"] > #absentPanel {
display: flex; flex-direction: column;
}
.layout[data-layout="side"] > #tablePanel .table-scroll,
.layout[data-layout="side"] > #absentPanel .table-scroll {
flex: 1 1 auto; min-height: 0; max-height: none;
}
}
/* ================= 随机抽取:历史记录 ================= */
.log-list { display: flex; flex-direction: column; gap: 10px; }
.log-item {
border: 1px solid var(--border-2); border-radius: 10px;
background: #fbfcff; overflow: hidden;
}
.log-head {
display: flex; align-items: center; justify-content: space-between; gap: 10px;
padding: 10px 14px; flex-wrap: wrap;
}
.log-summary { font-size: 13px; font-weight: 600; color: var(--text); }
.log-summary small { color: var(--text-3); font-weight: 500; }
.log-actions { display: flex; gap: 8px; flex: none; }
.log-detail summary {
cursor: pointer; user-select: none; padding: 7px 14px;
font-size: 12px; color: var(--primary); border-top: 1px dashed var(--border-2);
list-style: none;
}
.log-detail summary::-webkit-details-marker { display: none; }
.log-detail summary::before { content: '▸ '; }
.log-detail[open] summary::before { content: '▾ '; }
.log-detail[open] summary { border-bottom: 1px solid var(--border-2); }
.log-detail .table-scroll { max-height: 260px; border-radius: 0; }