Files
digital-psychology/scale_test.html
T

317 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>量表测试 — 愈心谷</title>
<style>
:root{--pri:#E54D42;--bg-start:#FFD1C7;--bg-end:#FFC8B5}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 50%,#fff5f0 100%);
min-height:100vh;-webkit-font-smoothing:antialiased;
display:flex;flex-direction:column;
}
.top-bar{display:flex;align-items:center;padding:14px 16px;gap:12px;max-width:500px;margin:0 auto}
.top-bar .back{font-size:22px;text-decoration:none;color:#666;line-height:1}
.top-bar .title{flex:1;font-size:16px;font-weight:600;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:center}
.top-bar .qnum{font-size:13px;color:#999}
.progress{height:3px;background:rgba(0,0,0,0.08);max-width:500px;margin:0 auto}
.progress .bar{height:100%;background:var(--pri);transition:width 0.3s}
.main{flex:1;display:flex;flex-direction:column;padding:20px;max-width:500px;width:100%;margin:0 auto}
.qcard{background:#fff;border-radius:14px;padding:28px 22px;box-shadow:0 2px 10px rgba(0,0,0,0.04);flex:1}
.qcard .qtext{font-size:17px;color:#333;line-height:1.7;margin-bottom:24px}
.qcard .qtext .qidx{color:var(--pri);font-weight:600;margin-right:6px}
.options{display:flex;flex-direction:column;gap:10px}
.option{
padding:14px 18px;border-radius:10px;border:1.5px solid #eee;
font-size:15px;color:#444;cursor:pointer;transition:all 0.15s;text-align:left;background:#fafafa;
}
.option:hover{border-color:var(--pri);background:#fff5f3}
.option.selected{border-color:var(--pri);background:#fff0ed;color:var(--pri);font-weight:500}
.btns{display:flex;gap:12px;padding:16px 0 30px;max-width:500px;width:100%;margin:0 auto}
.btns button{
flex:1;padding:14px;border-radius:10px;border:none;font-size:15px;cursor:pointer;
font-weight:500;transition:all 0.15s;
}
.btn-prev{background:#f0f0f0;color:#666}
.btn-next{background:var(--pri);color:#fff}
.btn-submit{background:#4CAF50;color:#fff}
button:disabled{opacity:0.4;cursor:not-allowed}
.submit-area{text-align:center;padding:40px 20px}
.submit-area p{font-size:14px;color:#888;margin-bottom:20px}
.submit-area button{
padding:14px 40px;border-radius:10px;background:var(--pri);color:#fff;
border:none;font-size:16px;cursor:pointer;font-weight:500;
}
.result-card{background:#fff;border-radius:14px;padding:32px 22px;box-shadow:0 2px 10px rgba(0,0,0,0.04);text-align:center;max-width:500px;width:100%;margin:0 auto}
.result-card .score{font-size:48px;font-weight:700;color:var(--pri);margin:12px 0}
.result-card .level{font-size:18px;color:#666;margin-bottom:20px}
.result-card .detail{font-size:14px;color:#999;line-height:1.6;text-align:left}
.result-card .back-btn{
display:inline-block;margin-top:20px;padding:12px 30px;background:var(--pri);color:#fff;
border-radius:10px;text-decoration:none;font-size:15px;
}
.loading{text-align:center;padding:80px 20px;color:#999}
</style>
</head>
<body>
<div id="app"><div class="loading">加载中…</div></div>
<script>
const params = new URLSearchParams(location.search);
const slug = params.get('slug');
let scale = null, answers = [], current = 0, submitted = false, started = false;
async function init() {
if (!slug) { document.getElementById('app').innerHTML = '<div class="loading">缺少量表参数</div>'; return; }
try {
const r = await fetch(`api/scales/${slug}`);
scale = await r.json();
if (scale.error) throw new Error(scale.error);
answers = new Array(scale.question_count).fill(null);
// Restore from localStorage
const cached = localStorage.getItem(`scale_${slug}`);
if (cached) answers = JSON.parse(cached);
renderStart();
} catch(e) {
document.getElementById('app').innerHTML = `<div class="loading">加载失败: ${e.message}</div>`;
}
}
function renderStart() {
const hasCache = answers.some(a => a !== null);
const answered = answers.filter(a => a !== null).length;
const rawDesc = scale.description || '';
const parts = rawDesc.split('\n\n');
const mainDesc = parts[0] || '';
const metaLine = parts.slice(1).join(' · ') || '';
const timeMin = Math.ceil(scale.question_count / 6);
const timeMax = Math.ceil(scale.question_count / 4);
document.getElementById('app').innerHTML = `
<div style="max-width:500px;margin:0 auto;position:relative;text-align:center;padding:32px 20px 8px">
<a href="scales.html" style="position:absolute;left:20px;top:34px;color:#999;font-size:20px;text-decoration:none;line-height:1">←</a>
<div style="font-size:20px;font-weight:700;color:#333">${scale.name}</div>
</div>
<div class="main" style="justify-content:center;padding:8px 20px 16px">
<div class="qcard" style="text-align:left;flex:none;padding:24px 20px">
${mainDesc ? `<div style="font-size:15px;color:#444;line-height:1.85;margin-bottom:16px">${mainDesc}</div>` : ''}
${metaLine ? `<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px">
${metaLine.split('·').filter(m=>m.trim()).map(m =>
`<span style="display:inline-block;padding:4px 10px;background:#f5f5f5;border-radius:6px;font-size:12px;color:#888">${m.trim()}</span>`
).join('')}
</div>` : ''}
<div style="display:flex;align-items:center;gap:12px;padding:12px 14px;background:#f9f9f9;border-radius:8px;margin-bottom:16px;font-size:13px;color:#888">
<span>📝 <b style="color:#555">${scale.question_count}</b> 题</span>
<span>⏱ ${timeMin}-${timeMax} 分钟</span>
<span>🔒 自动保存</span>
</div>
${hasCache
? `<div style="background:#fff5f3;padding:12px;border-radius:8px;margin-bottom:14px;font-size:13px;color:var(--pri);text-align:center">
📌 已有答题进度:<b>${answered}/${scale.question_count}</b> 题
</div>`
: ''}
<button onclick="startTest()" style="display:block;width:100%;padding:15px;border:none;border-radius:10px;background:var(--pri);color:#fff;font-size:17px;font-weight:600;cursor:pointer">
${hasCache ? `继续测试 (${answered}/${scale.question_count})` : '开始测试'}
</button>
${hasCache
? `<button onclick="resetAndStart()" style="display:block;width:100%;padding:12px;border:1px solid #ddd;border-radius:10px;background:#fff;color:#999;font-size:14px;cursor:pointer;margin-top:8px">重新开始</button>`
: ''}
</div>
</div>
`;
}
function startTest() {
started = true;
// Resume from first unanswered question
const firstUnanswered = answers.findIndex(a => a === null);
current = firstUnanswered >= 0 ? firstUnanswered : 0;
render();
}
function resetAndStart() {
answers = new Array(scale.question_count).fill(null);
localStorage.removeItem(`scale_${slug}`);
started = true;
current = 0;
render();
}
function saveCache() {
localStorage.setItem(`scale_${slug}`, JSON.stringify(answers));
}
function selectOption(idx) {
if (submitted) return;
answers[current] = idx;
saveCache();
render();
// Auto advance after short delay
if (current < scale.question_count - 1) {
setTimeout(() => { current++; render(); }, 300);
}
}
function goPrev() { if (current > 0) { current--; render(); } }
function goNext() { if (current < scale.question_count - 1) { current++; render(); } }
async function submit() {
if (submitted) return;
submitted = true;
saveCache();
try {
const r = await fetch('api/scales/result', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
scale_slug: scale.slug,
scale_name: scale.name,
answers: answers,
})
});
const data = await r.json();
// Clear cache
localStorage.removeItem(`scale_${slug}`);
renderResult(data);
} catch(e) {
submitted = false;
alert('提交失败,请重试');
render();
}
}
function renderResult(data) {
const s = data.scores;
const allAnswered = s.answer_count;
const total = scale.question_count;
const scoreDisplay = s.std_score !== null ? s.std_score : s.raw_score;
const isStd = s.std_score !== null;
const isFactorScale = s.factors && Object.keys(s.factors).length >= 6;
const hasFactors = s.factors && Object.keys(s.factors).length > 0;
// Factor bars (for any factor count, not just 8+)
let factorsHtml = '';
if (hasFactors) {
const factorMax = s.factor_max || {};
const bars = Object.entries(s.factors).map(([k,v]) => {
const max = factorMax[k] || 50;
const pct = Math.min(Math.round(v / max * 100), 100);
return `<div style="margin:6px 0">
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:2px">
<span style="color:#666">${k}</span><span style="color:var(--pri);font-weight:600">${v}<span style="color:#bbb;font-weight:400">/${max}</span></span>
</div>
<div style="height:6px;background:#f0f0f0;border-radius:3px;overflow:hidden">
<div style="height:100%;width:${pct}%;background:linear-gradient(90deg,var(--pri),#F0987A);border-radius:3px;transition:width 0.5s"></div>
</div>
</div>`;
}).join('');
let insightHtml = '';
if (isFactorScale) {
const sorted = Object.entries(s.factors).sort((a,b) => b[1]-a[1]);
const top3 = sorted.slice(0,3).map(([k,v]) => `<b>${k}</b>`).join('、');
insightHtml = `<div style="margin-top:14px;padding:12px;background:#fff5f3;border-radius:8px;font-size:13px;line-height:1.7">
🌟 <b>优势方向:</b>${top3}<br>
<span style="color:#999">得分最高的领域代表天赋倾向,建议重点关注和培养。</span>
</div>`;
}
factorsHtml = `<div style="margin:20px 0 0;text-align:left">
<div style="font-size:14px;font-weight:600;color:#555;margin-bottom:8px">${isFactorScale ? '各维度得分' : '因子得分'}</div>
${bars}
${insightHtml}
</div>`;
}
// Rating badge
let ratingHtml = '';
if (s.rating) {
ratingHtml = `<div style="display:inline-block;margin-top:8px;padding:6px 18px;background:${isFactorScale?'#fff5f3':'#fef0ed'};border-radius:20px;font-size:15px;font-weight:600;color:var(--pri)">${s.rating}</div>`;
}
document.getElementById('app').innerHTML = `
<div class="top-bar">
<a href="scales.html" class="back">←</a>
<div class="title">${scale.name}</div>
</div>
<div class="result-card" style="text-align:center">
<div style="font-size:13px;color:#bbb;margin-bottom:4px">测评完成 · ${allAnswered}/${total} 题</div>
${isFactorScale
? `<div style="font-size:22px;font-weight:700;color:#333;margin:8px 0 4px">多元智能测评结果</div>`
: `<div style="margin:8px 0">
<span style="font-size:56px;font-weight:700;color:var(--pri);line-height:1">${scoreDisplay}</span>
${isStd ? `<span style="font-size:16px;color:#999;margin-left:4px">分</span>` : ''}
</div>
${isStd ? `<div style="font-size:12px;color:#bbb;margin-top:-4px">原始分 ${s.raw_score} × 1.25 = 标准分 ${scoreDisplay}</div>` : ''}
${!isStd && !isFactorScale ? `<div style="font-size:13px;color:#999">满分 ${s.max_score} · 占比 ${(s.raw_score/s.max_score*100).toFixed(0)}%</div>` : ''}`}
${ratingHtml}
${factorsHtml}
<div style="margin-top:24px;display:flex;gap:10px;justify-content:center">
<a href="scales.html" style="flex:1;padding:12px;background:var(--pri);color:#fff;border-radius:10px;text-decoration:none;font-size:14px;font-weight:500">返回量表库</a>
<a href="scale_test.html?slug=${slug}" style="flex:1;padding:12px;background:#f5f5f5;color:#666;border-radius:10px;text-decoration:none;font-size:14px">重新测试</a>
</div>
<p style="margin-top:16px;color:#ccc;font-size:11px">* 结果仅供参考,不能替代专业诊断</p>
</div>
`;
}
function render() {
if (!scale) return;
const total = scale.question_count;
const pct = ((current + 1) / total * 100).toFixed(0);
const isLast = current === total - 1;
const selected = answers[current];
const q = scale.questions[current];
const opts = scale.options[current];
const optHtml = opts.map((o, i) => `
<button class="option${selected === i ? ' selected' : ''}" onclick="selectOption(${i})">
${String.fromCharCode(65+i)}. ${o}
</button>
`).join('');
const allAnswered = answers.every(a => a !== null);
document.getElementById('app').innerHTML = `
<div class="top-bar">
<a href="scales.html" class="back">←</a>
<div class="title">${scale.name}</div>
<div class="qnum">${current+1}/${total}</div>
</div>
<div class="progress"><div class="bar" style="width:${pct}%"></div></div>
<div class="main">
<div class="qcard">
<div class="qtext"><span class="qidx">${current+1}.</span>${q}</div>
<div class="options">${optHtml}</div>
</div>
</div>
<div class="btns">
<button class="btn-prev" onclick="goPrev()" ${current===0?'disabled':''}>上一题</button>
${isLast
? `<button class="btn-submit" onclick="submit()" ${allAnswered?'':'disabled'}>✓ 提交答卷</button>`
: `<button class="btn-next" onclick="goNext()" ${selected===null?'disabled':''}>下一题 →</button>`
}
</div>
${isLast ? `<div style="text-align:center;padding:0 20px 20px;font-size:12px;color:#999">${allAnswered?'全部答完,可以提交了':'还有题目未作答'}</div>` : ''}
`;
}
init();
</script>
</body>
</html>