b6df028839
变更摘要: - [fix] 清理根目录 index.html 遗留代码(questionnaire-error + retryLoad 移除) - [fix] 副标题同步更新为自估591分 + 候选志愿546条数据校准 - [fix] 页脚注入版本号 v1.0.0 - [fix] 部署版 index.html: 取消家长标签,统一 filler-tianheng - [fix] Dashboard 文案修正(4份→2份线上问卷) - [fix] 清理冗余部署脚本(v1-v4归档至 archived_scripts/) - [fix] 等效位次工具纳入 deploy 目录 - [chore] .gitignore 初始化 - [init] Git 仓库初始化
284 lines
8.6 KiB
HTML
284 lines
8.6 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>问卷汇总 — 天恒高考志愿门户</title>
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<style>
|
||
/* 汇总页特有样式 */
|
||
.member-card {
|
||
background: var(--card);
|
||
border-radius: var(--radius);
|
||
padding: 20px;
|
||
margin-bottom: 16px;
|
||
border: 1px solid var(--border);
|
||
}
|
||
.member-card h3 {
|
||
font-size: 16px;
|
||
color: var(--blue);
|
||
margin-bottom: 4px;
|
||
}
|
||
.member-card .time {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 12px;
|
||
}
|
||
.answer-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 8px;
|
||
}
|
||
.answer-item {
|
||
padding: 8px 12px;
|
||
background: var(--bg);
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
}
|
||
.answer-item .q-label {
|
||
color: var(--text-muted);
|
||
font-size: 11px;
|
||
margin-bottom: 2px;
|
||
}
|
||
.answer-item .q-val {
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
}
|
||
.highlight-diff {
|
||
background: #FFF3E0;
|
||
border-left: 3px solid var(--amber);
|
||
}
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px 0;
|
||
color: var(--text-muted);
|
||
}
|
||
/* 对比表格 */
|
||
.compare-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
margin-top: 12px;
|
||
}
|
||
.compare-table th {
|
||
background: var(--blue-light);
|
||
padding: 10px 12px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
font-size: 12px;
|
||
}
|
||
.compare-table td {
|
||
padding: 10px 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
vertical-align: top;
|
||
}
|
||
.compare-table tr:hover td {
|
||
background: #fafbfc;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
|
||
<div id="app">
|
||
<div id="loading" style="text-align:center;padding:60px;">
|
||
<p style="color:var(--text-muted);">加载汇总数据...</p>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
// ============================================================
|
||
// 问卷结果汇总页 — 展示所有成员的答卷,并高亮差异
|
||
// ============================================================
|
||
|
||
const params = new URLSearchParams(window.location.search);
|
||
const qid = params.get('id');
|
||
|
||
if (!qid) {
|
||
document.getElementById('app').innerHTML =
|
||
'<div style="text-align:center;padding:60px;"><p style="color:var(--red);">缺少问卷ID</p><a href="/" class="btn btn-primary" style="margin-top:16px;">← 返回首页</a></div>';
|
||
} else {
|
||
loadResults(qid);
|
||
}
|
||
|
||
async function loadResults(qid) {
|
||
try {
|
||
// 并行加载问卷信息和答案
|
||
const [qResp, rResp] = await Promise.all([
|
||
fetch(`/api/questionnaire/${qid}`),
|
||
fetch(`/api/results/${qid}`)
|
||
]);
|
||
|
||
if (!qResp.ok) throw new Error('问卷不存在');
|
||
const questionnaire = await qResp.json();
|
||
const results = await rResp.json();
|
||
|
||
let html = `
|
||
<div style="margin-bottom:20px;display:flex;justify-content:space-between;align-items:center;">
|
||
<a href="/" style="color:var(--blue);text-decoration:none;font-size:13px;">← 返回首页</a>
|
||
<a href="/questionnaire.html?id=${qid}" class="btn btn-outline btn-sm">📝 我也填一份</a>
|
||
</div>
|
||
<div class="hero" style="padding:20px 0 16px;">
|
||
<h1>📊 ${questionnaire.title} · 汇总</h1>
|
||
<p class="sub">共 ${results.length} 份答卷</p>
|
||
</div>
|
||
`;
|
||
|
||
if (results.length === 0) {
|
||
html += '<div class="empty-state"><p>📭 还没有人填写这份问卷</p><a href="/questionnaire.html?id=' + qid + '" class="btn btn-primary" style="margin-top:16px;">✍️ 我来填第一份</a></div>';
|
||
} else {
|
||
// 判断问卷类型,选择合适的展示方式
|
||
const qData = questionnaire.questions;
|
||
|
||
if (qData.type === 'likert') {
|
||
// Holland 评估:按维度汇总得分
|
||
html += renderHollandSummary(qData, results);
|
||
} else {
|
||
// 普通选择题问卷:横向对比表格
|
||
html += renderComparisonTable(qData, results);
|
||
}
|
||
}
|
||
|
||
document.getElementById('app').innerHTML = html;
|
||
|
||
} catch (err) {
|
||
document.getElementById('app').innerHTML = `
|
||
<div style="text-align:center;padding:60px;">
|
||
<p style="color:var(--red);">加载失败:${err.message}</p>
|
||
<a href="/" class="btn btn-primary" style="margin-top:16px;">← 返回首页</a>
|
||
</div>`;
|
||
}
|
||
}
|
||
|
||
// ============ Holland 评估:雷达图 + 维度得分表 ============
|
||
|
||
function renderHollandSummary(qData, results) {
|
||
const dims = qData.dimensions;
|
||
const dimNames = { R: '现实型', I: '研究型', A: '艺术型', S: '社会型', E: '企业型', C: '常规型' };
|
||
const dimDescs = {
|
||
R: '动手操作·机械·户外',
|
||
I: '分析思考·科学·研究',
|
||
A: '创造表达·艺术·审美',
|
||
S: '助人教学·沟通·服务',
|
||
E: '领导说服·管理·商业',
|
||
C: '秩序规范·数据·流程'
|
||
};
|
||
|
||
let html = '<div class="section-title">🎯 各维度得分对比</div>';
|
||
|
||
// 每个人一行卡片
|
||
results.forEach(r => {
|
||
// 按维度汇总得分
|
||
const scores = {};
|
||
dims.forEach(d => { scores[d] = 0; });
|
||
let count = {};
|
||
|
||
Object.entries(r.answers).forEach(([qid, val]) => {
|
||
const item = qData.items.find(i => i.id === qid);
|
||
if (item) {
|
||
scores[item.dimension] = (scores[item.dimension] || 0) + parseInt(val);
|
||
count[item.dimension] = (count[item.dimension] || 0) + 1;
|
||
}
|
||
});
|
||
|
||
// 算平均分
|
||
dims.forEach(d => {
|
||
scores[d] = count[d] ? (scores[d] / count[d]).toFixed(1) : 0;
|
||
});
|
||
|
||
// 找出最高分维度(前2)
|
||
const sorted = dims.map(d => ({ dim: d, score: parseFloat(scores[d]) }))
|
||
.sort((a, b) => b.score - a.score);
|
||
const top2 = sorted.slice(0, 2);
|
||
|
||
html += `
|
||
<div class="member-card">
|
||
<h3>👤 ${r.user_name}</h3>
|
||
<p class="time">提交时间:${r.created_at}</p>
|
||
<p style="font-size:14px;margin-bottom:12px;">
|
||
🏆 主导类型:<strong>${dimNames[top2[0].dim]}(${top2[0].score}分)</strong>、
|
||
${dimNames[top2[1].dim]}(${top2[1].score}分)
|
||
</p>
|
||
<table class="compare-table">
|
||
<tr>
|
||
${dims.map(d => `<th>${dimNames[d]}<br><span style="font-weight:400;font-size:10px;">${dimDescs[d]}</span></th>`).join('')}
|
||
</tr>
|
||
<tr>
|
||
${dims.map(d => {
|
||
const score = parseFloat(scores[d]);
|
||
const isTop = top2.some(t => t.dim === d);
|
||
return `<td style="text-align:center;${isTop ? 'font-weight:700;color:var(--blue);' : ''}">
|
||
${'⭐'.repeat(Math.round(score))}<br>${scores[d]} 分
|
||
</td>`;
|
||
}).join('')}
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
return html;
|
||
}
|
||
|
||
// ============ 普通问卷:横向对比表格 ============
|
||
|
||
function renderComparisonTable(qData, results) {
|
||
let html = '<div class="section-title">📋 逐题对比</div>';
|
||
|
||
// 构建对比表格:行=题目,列=成员
|
||
const members = results.map(r => r.user_name);
|
||
const memberAnswers = {};
|
||
results.forEach(r => {
|
||
memberAnswers[r.user_name] = r.answers;
|
||
});
|
||
|
||
html += '<div style="overflow-x:auto;">';
|
||
html += '<table class="compare-table">';
|
||
html += '<tr><th style="min-width:40px;">#</th><th style="min-width:200px;">题目</th>';
|
||
members.forEach(m => {
|
||
html += `<th style="min-width:150px;">${m}</th>`;
|
||
});
|
||
html += '</tr>';
|
||
|
||
qData.forEach((q, idx) => {
|
||
// 检查所有成员的答案是否一致
|
||
const values = members.map(m => memberAnswers[m] ? memberAnswers[m][q.id] || '-' : '-');
|
||
const allSame = values.every(v => v === values[0] && v !== '-');
|
||
const someBlank = values.some(v => v === '-');
|
||
|
||
html += `<tr class="${!allSame && !someBlank ? 'highlight-diff' : ''}">`;
|
||
html += `<td>${idx + 1}</td>`;
|
||
html += `<td style="font-size:12px;">${q.text}</td>`;
|
||
members.forEach(m => {
|
||
const val = memberAnswers[m] ? (memberAnswers[m][q.id] || '-') : '-';
|
||
// 把选项值映射为标签
|
||
let display = val;
|
||
if (q.options && val !== '-') {
|
||
const vals = val.split(',');
|
||
display = vals.map(v => {
|
||
const opt = q.options.find(o => o.value === v.trim());
|
||
return opt ? opt.label : v;
|
||
}).join('、');
|
||
}
|
||
html += `<td>${display}</td>`;
|
||
});
|
||
html += '</tr>';
|
||
});
|
||
|
||
html += '</table></div>';
|
||
|
||
// 差异提示
|
||
html += `
|
||
<div style="background:#FFF3E0;border-radius:8px;padding:12px 16px;margin-top:16px;font-size:13px;color:var(--amber);">
|
||
💡 橙色高亮行 = 家人之间答案不一致的题目。这些差异点建议在填报前专门讨论,达成共识。
|
||
</div>
|
||
`;
|
||
|
||
return html;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|