269 lines
11 KiB
PHP
269 lines
11 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
require __DIR__ . '/bootstrap.php';
|
||
|
||
$cfg = ny_load_config();
|
||
$rosterCount = Roster::count();
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title><?= htmlspecialchars(NY_APP_NAME) ?></title>
|
||
<link rel="stylesheet" href="assets/style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<header class="topbar">
|
||
<div class="brand">
|
||
<span class="logo">NY</span>
|
||
<div class="brand-text">
|
||
<strong>NY问卷考试系统数据平台</strong>
|
||
<small>卷王问卷系统 · 实时数据统计</small>
|
||
</div>
|
||
</div>
|
||
<div class="top-right">
|
||
<div id="conn" class="conn conn-off"><i></i><span>未连接</span></div>
|
||
<button class="btn" id="btnFullscreen" title="切换全屏">⛶ 全屏</button>
|
||
<button class="btn" id="btnRoster">名单管理 <b id="rosterBadge"><?= (int)$rosterCount ?></b></button>
|
||
<button class="btn btn-primary" id="btnSettings">设置</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ================= 登录门 ================= -->
|
||
<div class="login-gate" id="loginGate">
|
||
<div class="login-box">
|
||
<div class="login-brand">
|
||
<span class="logo">NY</span>
|
||
<div class="brand-text">
|
||
<strong>NY问卷考试系统数据平台</strong>
|
||
<small>卷王问卷系统 · 实时数据统计</small>
|
||
</div>
|
||
</div>
|
||
<p class="login-tip">请输入卷王系统连接信息,完成连接测试后登录。密码需每次输入,避免依赖一次配置直接查看数据。</p>
|
||
<div class="fi"><label>系统网址</label><input type="text" id="lgBase" placeholder="https://exam.nxxy335.top"></div>
|
||
<div class="fi"><label>API 路由</label><input type="text" id="lgPrefix" placeholder="/api"></div>
|
||
<div class="fi"><label>账号</label><input type="text" id="lgUser" placeholder="admin"></div>
|
||
<div class="fi"><label>密码</label><input type="password" id="lgPwd" placeholder="请输入密码" autocomplete="current-password"></div>
|
||
<div class="login-actions">
|
||
<button class="btn" id="lgTest">测试连接</button>
|
||
<button class="btn btn-primary" id="lgLogin" disabled>登录</button>
|
||
</div>
|
||
<div class="login-msg" id="lgMsg"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<main class="wrap">
|
||
|
||
<section class="toolbar">
|
||
<div class="field grow">
|
||
<label>问卷 / 考试</label>
|
||
<select id="projectSel"><option value="">加载中…</option></select>
|
||
</div>
|
||
<div class="field">
|
||
<label>姓名字段</label>
|
||
<select id="nameFieldSel"><option value="">自动识别</option></select>
|
||
</div>
|
||
<div class="field">
|
||
<label>班级</label>
|
||
<select id="clsSel"><option value="">全部班级</option></select>
|
||
</div>
|
||
<div class="field">
|
||
<label>自动刷新</label>
|
||
<div class="inline">
|
||
<label class="switch"><input type="checkbox" id="autoRefresh" checked><span></span></label>
|
||
<select id="intervalSel">
|
||
<option value="5">5 秒</option>
|
||
<option value="10">10 秒</option>
|
||
<option value="15" selected>15 秒</option>
|
||
<option value="30">30 秒</option>
|
||
<option value="60">60 秒</option>
|
||
<option value="120">2 分钟</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="field btns">
|
||
<button class="btn btn-primary" id="btnRefresh">立即刷新</button>
|
||
</div>
|
||
<div class="field grow-2">
|
||
<label>搜索</label>
|
||
<input type="text" id="kw" placeholder="输入学号或姓名筛选">
|
||
</div>
|
||
<div class="field btns">
|
||
<button class="btn" id="btnExport">导出成绩</button>
|
||
<button class="btn" id="btnExportAbsent">导出缺考</button>
|
||
</div>
|
||
</section>
|
||
|
||
<div id="alert" class="alert hidden"></div>
|
||
|
||
<section class="cards" id="statCards">
|
||
<div class="card"><span>名单总人数</span><b id="sTotal">-</b></div>
|
||
<div class="card ok"><span>已提交</span><b id="sSubmit">-</b></div>
|
||
<div class="card warn"><span>缺考</span><b id="sAbsent">-</b></div>
|
||
<div class="card"><span>提交率</span><b id="sRate">-</b></div>
|
||
<div class="card"><span>平均分</span><b id="sAvg">-</b></div>
|
||
<div class="card"><span>最高分</span><b id="sMax">-</b></div>
|
||
<div class="card"><span>最低分</span><b id="sMin">-</b></div>
|
||
<div class="card"><span>合格率(≥60%满分)</span><b id="sPass">-</b></div>
|
||
<div class="card"><span>平均用时</span><b id="sDur">-</b></div>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<div class="panel-head">
|
||
<h2>成绩明细 <small id="tableHint"></small></h2>
|
||
<div class="updated">最后更新:<span id="lastUpdate">—</span><i id="liveDot" class="dot"></i></div>
|
||
</div>
|
||
<div class="table-scroll">
|
||
<table class="grid" id="dataTable">
|
||
<thead>
|
||
<tr>
|
||
<th class="sortable" data-k="sid">学号</th>
|
||
<th class="sortable" data-k="name">姓名</th>
|
||
<th class="sortable" data-k="cls">班级</th>
|
||
<th class="sortable" data-k="score">成绩</th>
|
||
<th class="sortable" data-k="submit_ts">提交时间</th>
|
||
<th class="sortable" data-k="duration">考试时长</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody"><tr><td colspan="6" class="empty">请选择问卷 / 考试</td></tr></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="grid-2">
|
||
<section class="panel">
|
||
<div class="panel-head"><h2>缺考名单 <small id="absentHint"></small></h2></div>
|
||
<div class="table-scroll short">
|
||
<table class="grid compact">
|
||
<thead><tr><th>学号</th><th>姓名</th><th>班级</th></tr></thead>
|
||
<tbody id="absentBody"><tr><td colspan="3" class="empty">—</td></tr></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
<section class="panel">
|
||
<div class="panel-head"><h2>未匹配答卷 <small>姓名不在名单中</small></h2></div>
|
||
<div class="table-scroll short">
|
||
<table class="grid compact">
|
||
<thead><tr><th>姓名</th><th>成绩</th><th>提交时间</th></tr></thead>
|
||
<tbody id="unmatchedBody"><tr><td colspan="3" class="empty">—</td></tr></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- ================= 设置 ================= -->
|
||
<div class="modal" id="settingsModal">
|
||
<div class="modal-box">
|
||
<div class="modal-head"><h3>系统设置</h3><button class="x" data-close>×</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-grid">
|
||
<div class="fi full">
|
||
<label>卷王系统地址</label>
|
||
<input type="text" id="cfgBase" placeholder="http://127.0.0.1:1991">
|
||
<p class="hint">例如 http://192.168.1.10:1991,结尾不要带 /</p>
|
||
</div>
|
||
<div class="fi">
|
||
<label>接口前缀</label>
|
||
<input type="text" id="cfgPrefix" placeholder="/api">
|
||
<p class="hint">社区版默认 /api(企业版可填 /admin-api)</p>
|
||
</div>
|
||
<div class="fi">
|
||
<label>每页拉取条数</label>
|
||
<input type="number" id="cfgPageSize" min="20" max="1000">
|
||
</div>
|
||
<div class="fi">
|
||
<label>登录账号</label>
|
||
<input type="text" id="cfgUser">
|
||
</div>
|
||
<div class="fi">
|
||
<label>登录密码</label>
|
||
<input type="password" id="cfgPwd" placeholder="留空表示不修改">
|
||
</div>
|
||
<div class="fi">
|
||
<label>默认刷新间隔(秒)</label>
|
||
<input type="number" id="cfgInterval" min="3" max="600">
|
||
</div>
|
||
<div class="fi">
|
||
<label>请求超时(秒)</label>
|
||
<input type="number" id="cfgTimeout" min="3" max="120">
|
||
</div>
|
||
<div class="fi">
|
||
<label>满分(用于合格率)</label>
|
||
<input type="number" id="cfgFull" min="1" max="1000">
|
||
<p class="hint" id="cfgFullHint">未选择试卷时,作为所有试卷的默认值</p>
|
||
</div>
|
||
<div class="fi">
|
||
<label>姓名题关键词</label>
|
||
<input type="text" id="cfgKeywords">
|
||
<p class="hint">英文逗号分隔,用于自动识别"姓名"题目</p>
|
||
</div>
|
||
<div class="fi full checks">
|
||
<label><input type="checkbox" id="cfgDedupe"> 同一学号只保留最新一份答卷</label>
|
||
<label><input type="checkbox" id="cfgTemp"> 包含"暂存"未提交的答卷</label>
|
||
<label><input type="checkbox" id="cfgSsl"> 校验 HTTPS 证书</label>
|
||
</div>
|
||
</div>
|
||
<div id="pingResult" class="ping-result hidden"></div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<button class="btn" id="btnPing">测试连接</button>
|
||
<button class="btn" id="btnDiag">详细诊断</button>
|
||
<span class="spacer"></span>
|
||
<button class="btn" data-close>取消</button>
|
||
<button class="btn btn-primary" id="btnSaveCfg">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= 名单管理 ================= -->
|
||
<div class="modal" id="rosterModal">
|
||
<div class="modal-box wide">
|
||
<div class="modal-head"><h3>学号名单管理</h3><button class="x" data-close>×</button></div>
|
||
<div class="modal-body">
|
||
<div class="upload-zone" id="dropZone">
|
||
<input type="file" id="rosterFile" accept=".xlsx,.xlsm,.csv,.txt" hidden>
|
||
<div class="uz-inner">
|
||
<strong>点击或拖拽文件到此上传</strong>
|
||
<span>支持 .xlsx / .csv,需包含「学号」「姓名」两列(「班级」可选)</span>
|
||
</div>
|
||
</div>
|
||
<div class="roster-bar">
|
||
<div class="inline">
|
||
<label class="chk"><input type="radio" name="impMode" value="replace" checked> 覆盖</label>
|
||
<label class="chk"><input type="radio" name="impMode" value="append"> 追加</label>
|
||
</div>
|
||
<input type="text" id="rosterSearch" placeholder="搜索学号 / 姓名">
|
||
<span class="spacer"></span>
|
||
<button class="btn sm" id="btnAddRow">+ 新增</button>
|
||
<button class="btn sm danger" id="btnClearRoster">清空</button>
|
||
<button class="btn sm btn-primary" id="btnSaveRoster">保存名单</button>
|
||
</div>
|
||
<div class="table-scroll mid">
|
||
<table class="grid compact" id="rosterTable">
|
||
<thead><tr><th style="width:32%">学号</th><th style="width:26%">姓名</th><th>班级</th><th style="width:70px">操作</th></tr></thead>
|
||
<tbody id="rosterBody"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="hint" id="rosterMeta">共 0 人</p>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<span class="spacer"></span>
|
||
<button class="btn" data-close>关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
window.NY = {
|
||
appName: <?= json_encode(NY_APP_NAME, JSON_UNESCAPED_UNICODE) ?>,
|
||
version: <?= json_encode(NY_VERSION, JSON_UNESCAPED_UNICODE) ?>,
|
||
interval: <?= (int)$cfg['refresh_interval'] ?>
|
||
};
|
||
</script>
|
||
<script src="assets/app.js"></script>
|
||
</body>
|
||
</html>
|