Files
ny-exam-platform/assets/style.css
T
nxxy335 94ac381579 完成批次 L-R:限流移除/白名单驱动/打印增强/全屏/表格对齐/主题变量/滚动闪烁修复
- 移除登录失败限流;fix-config.php 修正 e2e 串味的 127.0.0.1 连接

- 白名单驱动缺/待考名单(DataService + SurveyKingClient::getWhitelist,复用已取 survey)

- 打印成绩明细增强(范围/合格列默认关/去页脚);看板全屏占满;表格 vertical-align:middle;隐藏登陆账号;导入默认追加

- 主题变量收口(:root 新增 brand/primary-2/ok-2 等,替换硬编码渐变);删死规则

- 表头/数据列对齐修复(index 未匹配表、dashboard 成员榜、draw 结果/历史/排除表);看板占位卡片配色一致

- 修复滚动闪烁:row-drop-in 动画经 _dropAnim 门控,仅强制重绘时播放

- 测试:新增 test-whitelist.js;run-all.sh 全绿 132
2026-09-13 10:25:48 +08:00

763 lines
33 KiB
CSS
Raw 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; }
/* 多次刷题:成员榜可点击行 */
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; }
.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); }
.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; }
/* ================= 随机抽取页 ================= */
.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; }