206 lines
8.5 KiB
HTML
206 lines
8.5 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">
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container" id="app">
|
||
|
|
<div style="text-align:center;padding:60px 0;color:var(--text-muted);">
|
||
|
|
<p>正在加载报告...</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// ============================================================
|
||
|
|
// 报告查看器 — 支持两种模式:
|
||
|
|
// ?file=xxx.html → 在 iframe 中显示静态报告
|
||
|
|
// ?id=问卷ID → 调用 API 生成分析报告
|
||
|
|
// ============================================================
|
||
|
|
var params = new URLSearchParams(window.location.search);
|
||
|
|
var qid = params.get('id');
|
||
|
|
var file = params.get('file');
|
||
|
|
|
||
|
|
var app = document.getElementById('app');
|
||
|
|
|
||
|
|
if (file) {
|
||
|
|
// ===== 模式1: 静态HTML报告 =====
|
||
|
|
app.innerHTML =
|
||
|
|
'<div style="margin-bottom:14px;">' +
|
||
|
|
'<a href="/" class="back-link">← 返回首页</a>' +
|
||
|
|
'</div>' +
|
||
|
|
'<iframe class="report-frame" id="report-iframe" src="/reports/' + file + '" title="分析报告"></iframe>';
|
||
|
|
|
||
|
|
// 动态调整 iframe 高度
|
||
|
|
setupIframeResize();
|
||
|
|
} else if (qid) {
|
||
|
|
// ===== 模式2: API生成的问卷分析报告 =====
|
||
|
|
loadReport(qid);
|
||
|
|
} else {
|
||
|
|
app.innerHTML =
|
||
|
|
'<div style="text-align:center;padding:60px;">' +
|
||
|
|
'<p style="color:var(--red);">缺少报告参数</p>' +
|
||
|
|
'<a href="/" class="btn btn-primary">← 返回首页</a>' +
|
||
|
|
'</div>';
|
||
|
|
}
|
||
|
|
|
||
|
|
// ========== iframe 高度自适应 ==========
|
||
|
|
function setupIframeResize() {
|
||
|
|
var iframe = document.getElementById('report-iframe');
|
||
|
|
if (!iframe) return;
|
||
|
|
|
||
|
|
iframe.onload = function() {
|
||
|
|
try {
|
||
|
|
// 同源可直接访问 contentWindow
|
||
|
|
var doc = iframe.contentWindow.document;
|
||
|
|
var height = Math.max(
|
||
|
|
doc.body.scrollHeight,
|
||
|
|
doc.body.offsetHeight,
|
||
|
|
doc.documentElement.scrollHeight,
|
||
|
|
doc.documentElement.offsetHeight
|
||
|
|
);
|
||
|
|
iframe.style.height = (height + 40) + 'px';
|
||
|
|
} catch(e) {
|
||
|
|
// 跨域时退化为固定最小高度
|
||
|
|
console.warn('iframe 高度自适应失败(可能跨域):', e.message);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// ========== API报告生成逻辑 ==========
|
||
|
|
function loadReport(qid) {
|
||
|
|
fetch('/api/report/' + qid)
|
||
|
|
.then(function(resp) {
|
||
|
|
if (!resp.ok) throw new Error('生成失败');
|
||
|
|
return resp.json();
|
||
|
|
})
|
||
|
|
.then(function(report) {
|
||
|
|
var html =
|
||
|
|
'<div style="margin-bottom:14px;">' +
|
||
|
|
'<a href="/" class="back-link">← 返回首页</a>' +
|
||
|
|
'</div>' +
|
||
|
|
'<div class="hero" style="padding:20px 0 16px;">' +
|
||
|
|
'<h1>' + report.questionnaire_title + '</h1>' +
|
||
|
|
'<p class="sub">分析报告 · ' + report.member_count + '人参与 · ' + report.generated_at + '</p>' +
|
||
|
|
'</div>';
|
||
|
|
|
||
|
|
if (report.report_type === 'holland') {
|
||
|
|
html += renderHollandReport(report);
|
||
|
|
} else if (report.report_type === 'preference') {
|
||
|
|
html += renderPreferenceReport(report);
|
||
|
|
} else if (report.report_type === 'expectation') {
|
||
|
|
html += renderExpectationReport(report);
|
||
|
|
} else {
|
||
|
|
html += '<div class="alert alert-amber"><p>暂无分析数据</p></div>';
|
||
|
|
}
|
||
|
|
|
||
|
|
html += renderExportBlock(qid);
|
||
|
|
app.innerHTML = html;
|
||
|
|
})
|
||
|
|
.catch(function(err) {
|
||
|
|
app.innerHTML =
|
||
|
|
'<div style="text-align:center;padding:60px;">' +
|
||
|
|
'<p style="color:var(--red);">生成失败:' + err.message + '</p>' +
|
||
|
|
'<p style="color:var(--text-muted);font-size:13px;">请先填写问卷后再查看报告</p>' +
|
||
|
|
'<a href="/" class="btn btn-primary" style="margin-top:16px;">← 返回首页</a>' +
|
||
|
|
'</div>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderExportBlock(qid) {
|
||
|
|
return '' +
|
||
|
|
'<div style="margin-top:32px;padding:20px;background:var(--card);border-radius:var(--radius-md);border:1px solid var(--border);">' +
|
||
|
|
'<h3 style="margin:0 0 12px;color:var(--navy-700);">导出为后续分析</h3>' +
|
||
|
|
'<p style="color:var(--text-muted);font-size:13px;margin-bottom:12px;">点击下方按钮可导出JSON格式,供志愿方案分析使用</p>' +
|
||
|
|
'<button onclick="exportReport(' + qid + ')" class="btn btn-gold">导出 JSON</button>' +
|
||
|
|
'<div id="export-area" style="display:none;margin-top:16px;">' +
|
||
|
|
'<textarea id="export-json" readonly style="width:100%;height:200px;font-family:monospace;font-size:12px;padding:12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg);"></textarea>' +
|
||
|
|
'<p style="font-size:12px;color:var(--text-muted);margin-top:8px;">复制上方内容,或右键另存为 .json 文件</p>' +
|
||
|
|
'</div>' +
|
||
|
|
'</div>';
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderHollandReport(report) {
|
||
|
|
var data = report.data;
|
||
|
|
var conclusions = report.conclusions;
|
||
|
|
var names = {"R":"现实型","I":"研究型","A":"艺术型","S":"社会型","E":"企业型","C":"常规型"};
|
||
|
|
|
||
|
|
var html = '<div style="background:var(--card);border-radius:var(--radius-md);padding:20px;margin-bottom:20px;border:1px solid var(--border);"><h3 style="margin:0 0 16px;color:var(--navy-700);">Holland 职业兴趣分析</h3>';
|
||
|
|
|
||
|
|
if (data.dimension_scores) {
|
||
|
|
Object.entries(data.dimension_scores).forEach(function(entry) {
|
||
|
|
var member = entry[0];
|
||
|
|
var scores = entry[1];
|
||
|
|
var sorted = Object.entries(scores).sort(function(a,b) { return b[1] - a[1]; });
|
||
|
|
html += '<div style="margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--border);"><div style="font-weight:600;margin-bottom:8px;">' + member + '</div><div style="display:flex;gap:6px;flex-wrap:wrap;">' + sorted.map(function(s) { return '<span style="background:var(--navy-600);color:#fff;padding:4px 10px;border-radius:4px;font-size:13px;">' + names[s[0]] + ': ' + s[1] + '</span>'; }).join('') + '</div></div>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
conclusions.forEach(function(c) {
|
||
|
|
html += '<div style="padding:12px;background:var(--bg);border-radius:var(--radius-sm);margin-bottom:8px;border:1px solid var(--border);"><strong>' + c.member + '</strong>: ' + c.top_dims.join(' > ') + '</div>';
|
||
|
|
});
|
||
|
|
|
||
|
|
html += '</div>';
|
||
|
|
return html;
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderPreferenceReport(report) {
|
||
|
|
var data = report.data;
|
||
|
|
var html = '<div style="background:var(--card);border-radius:var(--radius-md);padding:20px;margin-bottom:20px;border:1px solid var(--border);"><h3 style="margin:0 0 16px;color:var(--navy-700);">院校偏好分析</h3>';
|
||
|
|
|
||
|
|
(data.consensus || []).forEach(function(c) {
|
||
|
|
html += '<div style="padding:8px 12px;background:var(--green-light);color:var(--green);border-radius:4px;margin-bottom:4px;font-size:13px;">✓ ' + c.question + ': ' + c.answer + '</div>';
|
||
|
|
});
|
||
|
|
(data.conflicts || []).forEach(function(c) {
|
||
|
|
var answers = Object.entries(c.answers).map(function(e) { return e[0] + ':' + e[1]; }).join(' vs ');
|
||
|
|
html += '<div style="padding:8px 12px;background:var(--amber-light);color:var(--amber);border-radius:4px;margin-bottom:4px;font-size:13px;">⚠ ' + c.question + ': ' + answers + '</div>';
|
||
|
|
});
|
||
|
|
|
||
|
|
html += '</div>';
|
||
|
|
return html;
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderExpectationReport(report) {
|
||
|
|
var data = report.data;
|
||
|
|
var html = '<div style="background:var(--card);border-radius:var(--radius-md);padding:20px;margin-bottom:20px;border:1px solid var(--border);"><h3 style="margin:0 0 16px;color:var(--navy-700);">家庭期望对齐分析</h3>';
|
||
|
|
|
||
|
|
var textAnswers = data.text || {};
|
||
|
|
Object.entries(textAnswers).forEach(function(entry) {
|
||
|
|
var q = entry[0];
|
||
|
|
var answers = entry[1];
|
||
|
|
Object.entries(answers).forEach(function(ae) {
|
||
|
|
var member = ae[0];
|
||
|
|
var text = ae[1];
|
||
|
|
html += '<div style="margin-bottom:12px;padding:8px 12px;background:var(--bg);border-radius:var(--radius-sm);"><strong>' + member + '</strong>: ' + text.substring(0,100) + (text.length > 100 ? '...' : '') + '</div>';
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
html += '</div>';
|
||
|
|
return html;
|
||
|
|
}
|
||
|
|
|
||
|
|
function exportReport(qid) {
|
||
|
|
fetch('/api/export/' + qid)
|
||
|
|
.then(function(resp) { return resp.json(); })
|
||
|
|
.then(function(data) {
|
||
|
|
var jsonStr = JSON.stringify(data, null, 2);
|
||
|
|
document.getElementById('export-json').value = jsonStr;
|
||
|
|
document.getElementById('export-area').style.display = 'block';
|
||
|
|
|
||
|
|
var blob = new Blob([jsonStr], {type: 'application/json'});
|
||
|
|
var url = URL.createObjectURL(blob);
|
||
|
|
var a = document.createElement('a');
|
||
|
|
a.href = url;
|
||
|
|
a.download = 'gaokao_q' + qid + '_report_' + Date.now() + '.json';
|
||
|
|
a.click();
|
||
|
|
})
|
||
|
|
.catch(function(err) {
|
||
|
|
alert('导出失败: ' + err.message);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|