317 lines
14 KiB
HTML
317 lines
14 KiB
HTML
<!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>
|