完成批次 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
This commit is contained in:
+47
-4
@@ -94,6 +94,36 @@ function rateColor(r){
|
||||
return 'var(--danger)';
|
||||
}
|
||||
|
||||
/* FLIP 行移动画:新数据行滑到新排名、其它行顺势下移(尊重 prefers-reduced-motion) */
|
||||
const REDUCED_MOTION = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
||||
function captureFlip(container){
|
||||
const m = new Map();
|
||||
if (!container) return m;
|
||||
container.querySelectorAll('tr[data-sid]').forEach(tr => {
|
||||
const sid = tr.getAttribute('data-sid');
|
||||
if (sid) m.set(sid, tr.getBoundingClientRect().top);
|
||||
});
|
||||
return m;
|
||||
}
|
||||
function playFlip(container, prev){
|
||||
if (!container || !prev || REDUCED_MOTION) return;
|
||||
container.querySelectorAll('tr[data-sid]').forEach(tr => {
|
||||
const sid = tr.getAttribute('data-sid');
|
||||
if (!sid) return;
|
||||
const oldTop = prev.get(sid);
|
||||
if (oldTop === undefined) { tr.classList.add('row-drop-in'); return; }
|
||||
const dy = oldTop - tr.getBoundingClientRect().top;
|
||||
if (Math.abs(dy) < 1) return;
|
||||
tr.style.transition = 'none';
|
||||
tr.style.transform = 'translateY(' + dy + 'px)';
|
||||
void tr.offsetWidth;
|
||||
requestAnimationFrame(() => {
|
||||
tr.style.transition = 'transform .42s cubic-bezier(.22,.61,.36,1)';
|
||||
tr.style.transform = '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* URL 状态:?pid=&class=&auto=1|0 */
|
||||
function readUrl(){
|
||||
const u = new URL(location.href);
|
||||
@@ -237,7 +267,7 @@ function membersTable(members){
|
||||
return '<div class="empty" style="padding:18px 0">暂无考试成员</div>';
|
||||
}
|
||||
return '<div class="member-list"><table class="grid compact">' +
|
||||
'<thead><tr><th style="width:60px">排名</th><th>姓名</th><th style="width:80px">成绩</th></tr></thead><tbody>' +
|
||||
'<thead><tr><th style="width:20%;text-align:right">排名</th><th style="width:55%">姓名</th><th style="width:25%" data-k="score">成绩</th></tr></thead><tbody>' +
|
||||
members.map(m => {
|
||||
const rk = (m.rank == null ? '' : m.rank);
|
||||
const badge = rk === 1 ? 'rk rk-1' : (rk === 2 ? 'rk rk-2' : (rk === 3 ? 'rk rk-3' : 'rk'));
|
||||
@@ -255,7 +285,7 @@ function membersTable(members){
|
||||
const tag = att ? ' <span class="tag tag-att" title="查看 ' + m.attempts + ' 次提交明细">刷' + m.attempts + '次</span>' : '';
|
||||
// 注意:不要把 sid 直接拼进 onclick="...",JSON.stringify 产生的双引号会提前闭合
|
||||
// HTML 属性(onclick="openAttempts("123")"),点击必然报错。改用 data-sid + 事件委托。
|
||||
return '<tr' + (att ? ' class="clickable" data-sid="' + esc(m.sid) + '" title="查看提交明细"' : '') + '>' +
|
||||
return '<tr' + (att ? ' class="clickable"' : '') + ' data-sid="' + esc(m.sid) + '"' + (att ? ' title="查看提交明细"' : '') + '>' +
|
||||
'<td class="num"><span class="' + badge + '">' + rk + '</span></td>' +
|
||||
'<td class="name">' + esc(m.name) + tag + '</td>' +
|
||||
'<td class="score num ' + cls + '">' + (sc == null ? '-' : sc) + '</td>' +
|
||||
@@ -280,7 +310,11 @@ function openAttempts(sid){
|
||||
}).join('');
|
||||
$('#attTitle').textContent = info.name + ' · 共 ' + (info.attempts || list.length) + ' 次提交';
|
||||
$('#attBody').innerHTML = list.length
|
||||
? '<table class="grid compact"><thead><tr><th>提交时间</th><th>成绩</th><th>考试时长</th></tr></thead><tbody>' + rows + '</tbody></table>'
|
||||
? '<table class="grid compact att-table"><thead><tr>' +
|
||||
'<th data-k="submit_ts" style="width:40%">提交时间</th>' +
|
||||
'<th data-k="score" style="width:30%">成绩</th>' +
|
||||
'<th data-k="duration" style="width:30%">考试时长</th>' +
|
||||
'</tr></thead><tbody>' + rows + '</tbody></table>'
|
||||
: '<div class="empty">无提交明细</div>';
|
||||
document.querySelector('#attemptsModal').classList.add('open');
|
||||
}
|
||||
@@ -306,13 +340,19 @@ document.addEventListener('keydown', e => {
|
||||
});
|
||||
|
||||
/* ---------- 看板占位(未选问卷/班级) ---------- */
|
||||
/* 占位卡片配色:与真实看板卡片(renderBoard)一一对应,保证空态左侧色条一致 */
|
||||
const PLACEHOLDER_ACCENT = {
|
||||
'总人数': 'accent-blue', '已提交': 'accent-green', '缺考': 'accent-orange',
|
||||
'提交率': 'accent-purple', '平均分': 'accent-teal', '合格率': 'accent-indigo'
|
||||
};
|
||||
function renderPlaceholderBoard(){
|
||||
const cards = [
|
||||
['总人数','-'],['已提交','-'],['缺考','-'],
|
||||
['提交率','-'],['平均分','-'],['合格率','-']
|
||||
];
|
||||
$('#boardSummary').innerHTML = cards.map(c =>
|
||||
'<div class="card"><span>' + c[0] + '</span><b>' + c[1] + '</b></div>').join('');
|
||||
// 占位卡片与真实看板卡片顺序、配色一致(accent-* 决定左侧色条),避免空态蓝条与真实卡片不一致
|
||||
'<div class="card ' + PLACEHOLDER_ACCENT[c[0]] + '"><span>' + c[0] + '</span><b>' + c[1] + '</b></div>').join('');
|
||||
$('#boardBody').innerHTML = '<div class="empty">请选择问卷与班级查看考试整体情况</div>';
|
||||
// 注意:heroTitle 已随导航栏精简移除,只保留 heroSub(承载当前问卷/班级提示)
|
||||
const _hs = document.getElementById('heroSub');
|
||||
@@ -480,7 +520,10 @@ async function refreshBoard(manual){
|
||||
sel.disabled = !classes.length;
|
||||
if (classes.length) { sel.value = classes[0]; CUR.cls = classes[0]; writeUrl({ cls: classes[0] }); }
|
||||
}
|
||||
// 刷新前捕获各行位置,刷新后做 FLIP:新数据行滑入、其它行下移
|
||||
const flipPrev = captureFlip($('#boardBody'));
|
||||
renderBoard();
|
||||
playFlip($('#boardBody'), flipPrev);
|
||||
$('#lastUpdate').textContent = fresh.updated_at || '-';
|
||||
markLive();
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user