feat: 全面打磨平台交互
- 名单导入容错:rowsToStudents 加去重/跳过统计、表头正则扩充、.0 尾巴清理;importFile 返回精确 count
- 看板:修复 .repeat(0) 空状态坏代码;新增实时点 stale 指示(手动/自动刷新失败变红 + 提示刷新中断);
手动刷新按钮加刷新中…反馈;记住自动刷新开关与间隔(localStorage + ?interval= 深链)
- 主统计页:后台静默刷新连续失败时连接指示转实时刷新中断告警;记住用户手动调整的刷新间隔
- 设置页:新增 config_test 端点 + 独立测试连接按钮(用弹窗内未保存凭据测连接,不落盘);
保存按钮加保存中…防重提交;登录失败不清旧 token
- 抽取页:重置结果加二次确认;新增复制名单按钮(两列制表符分隔,可粘到微信/Excel)
- 修复看板预先存在 JS 语法错误(少一个右括号导致整页 JS 失效)
This commit is contained in:
+57
-3
@@ -21,7 +21,8 @@
|
||||
sortAsc: false,
|
||||
timer: null,
|
||||
loading: false,
|
||||
lastSig: ''
|
||||
lastSig: '',
|
||||
connFail: 0
|
||||
};
|
||||
|
||||
/* ---------------- 本地存储 ---------------- */
|
||||
@@ -41,6 +42,7 @@
|
||||
const LS_LASTQID = 'ny_last_qid';
|
||||
const LS_FILTERS = 'ny_filters'; // {cls, grp} -> 跟随 projectId
|
||||
const LS_LAYOUT = 'ny_layout';
|
||||
const LS_INTERVAL = 'ny_interval'; // 用户手动调整的刷新间隔(跟随本机)
|
||||
const LS_ROSTER = 'ny_roster_cache'; // 名单 cache(用于首屏补齐班级/组别下拉)
|
||||
const dataKey = pid => 'ny_data_' + pid;
|
||||
|
||||
@@ -243,6 +245,7 @@
|
||||
try {
|
||||
const d = await api('data', { params: { projectId: pid, nameQid: $('#nameFieldSel').value } });
|
||||
applyData(d);
|
||||
state.connFail = 0;
|
||||
LS.set(dataKey(pid), d);
|
||||
LS.set(LS_LASTPID, pid);
|
||||
LS.set(LS_LASTQID, $('#nameFieldSel').value || '');
|
||||
@@ -259,6 +262,9 @@
|
||||
if (!silent) setConn('conn-err', '数据获取失败');
|
||||
showAlert(e.message);
|
||||
} else {
|
||||
// 后台静默刷新失败:保留缓存数据,但把连接指示转为"中断"告警,成功即恢复
|
||||
state.connFail++;
|
||||
if (state.connFail >= 1) setConn('conn-warn', '⚠️ 实时刷新中断 · 已显示本地缓存');
|
||||
showAlert('⚠️ 后台刷新失败,已显示本地缓存:' + e.message);
|
||||
}
|
||||
} finally {
|
||||
@@ -697,7 +703,10 @@
|
||||
// 用缓存数据先快速渲染,再后台拉新数据
|
||||
refresh();
|
||||
});
|
||||
$('#intervalSel').addEventListener('change', schedule);
|
||||
$('#intervalSel').addEventListener('change', () => {
|
||||
LS.set(LS_INTERVAL, parseInt($('#intervalSel').value, 10) || 15);
|
||||
schedule();
|
||||
});
|
||||
$('#autoRefresh').addEventListener('change', e => {
|
||||
if (e.target.checked) { schedule(); refresh(true); }
|
||||
else if (state.timer) clearInterval(state.timer);
|
||||
@@ -740,6 +749,8 @@
|
||||
|
||||
/* 设置保存 */
|
||||
$('#btnSaveCfg').addEventListener('click', async () => {
|
||||
const btnSave = $('#btnSaveCfg');
|
||||
btnSave.disabled = true; btnSave.textContent = '保存中…';
|
||||
const pass = $('#cfgPass').value;
|
||||
const payload = {
|
||||
page_size: parseInt($('#cfgPageSize').value, 10) || 200,
|
||||
@@ -758,7 +769,7 @@
|
||||
let resp;
|
||||
try {
|
||||
resp = await api('config_save', { method: 'POST', body: payload });
|
||||
} catch (e) { showAlert(e.message); return; }
|
||||
} catch (e) { showAlert(e.message); btnSave.disabled = false; btnSave.textContent = '保存'; return; }
|
||||
$('#cfgPass').value = ''; // 提交后清空密码框
|
||||
const note = (resp && resp.conn_changed) ? '账号/连接已更新' : '设置已保存';
|
||||
if (resp && resp.ping) {
|
||||
@@ -783,8 +794,44 @@
|
||||
if (!resp || !resp.ping || resp.ping.ok) {
|
||||
$('#settingsModal').classList.remove('open');
|
||||
}
|
||||
btnSave.disabled = false; btnSave.textContent = '保存';
|
||||
});
|
||||
|
||||
/* 独立测试连接(使用弹窗内尚未保存的账号/密码,不落盘) */
|
||||
async function testConnection() {
|
||||
const btn = $('#btnTestConn');
|
||||
const box = $('#pingResult');
|
||||
if (!btn || !box) return;
|
||||
btn.disabled = true; btn.textContent = '测试中…';
|
||||
box.classList.remove('hidden', 'err'); box.classList.add('loading');
|
||||
box.textContent = '正在连接卷王系统测试…';
|
||||
try {
|
||||
// 仅传可编辑的连接字段;base_url / api_prefix 在弹窗中只读,沿用已保存值
|
||||
const r = await api('config_test', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: $('#cfgUser').value.trim(),
|
||||
password: $('#cfgPass').value,
|
||||
verify_ssl: $('#cfgSsl').checked
|
||||
}
|
||||
});
|
||||
box.classList.remove('loading');
|
||||
if (r && r.connected) {
|
||||
box.classList.remove('err');
|
||||
box.textContent = '✅ 连接成功:检测到 ' + r.projects + ' 个问卷 / 用时 ' + r.elapsed + 'ms';
|
||||
} else {
|
||||
box.classList.add('err');
|
||||
box.textContent = '❌ 连接失败:' + ((r && r.msg) || '未知错误');
|
||||
}
|
||||
} catch (e) {
|
||||
box.classList.remove('loading'); box.classList.add('err');
|
||||
box.textContent = '❌ 测试请求异常:' + e.message;
|
||||
} finally {
|
||||
btn.disabled = false; btn.textContent = '测试连接';
|
||||
}
|
||||
}
|
||||
$('#btnTestConn').addEventListener('click', testConnection);
|
||||
|
||||
// 收集"各试卷独立满分" -> {projectId: score}
|
||||
function collectFullScores() {
|
||||
const out = {};
|
||||
@@ -871,6 +918,13 @@
|
||||
// 2) 支持 ?projectId=xxx&nameQid=yyy 直接打开某份问卷
|
||||
const qs = new URLSearchParams(location.search);
|
||||
if (qs.get('interval')) $('#intervalSel').value = qs.get('interval');
|
||||
else {
|
||||
const iv = LS.get(LS_INTERVAL, 0);
|
||||
if (iv) {
|
||||
const opts = $$('#intervalSel option').map(o => parseInt(o.value, 10));
|
||||
if (opts.indexOf(iv) >= 0) $('#intervalSel').value = String(iv);
|
||||
}
|
||||
}
|
||||
state.deeplinkProject = qs.get('projectId') || '';
|
||||
state.deeplinkQid = qs.get('nameQid') || '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user