Files
2026gaokaozhiyuan/MBTI性格测试-自评版.html
T
simon b6df028839 v1.0.0 高考志愿门户完整修复
变更摘要:
- [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 仓库初始化
2026-06-24 13:11:34 +08:00

646 lines
35 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-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MBTI性格测试 · 自评版</title>
<style>
:root {
--primary: #1a3a5c;
--primary-light: #2c5282;
--gold: #c9a84c;
--gold-light: #f0e6c8;
--bg: #f5f0e8;
--card: #ffffff;
--text: #2d3748;
--text-light: #718096;
--border: #e2e8f0;
--success: #38a169;
--ei-color: #805ad5;
--sn-color: #3182ce;
--tf-color: #dd6b20;
--jp-color: #38a169;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "PingFang SC","Microsoft YaHei","Noto Sans SC",sans-serif; background: var(--bg); color: var(--text); line-height: 1.8; padding-bottom: 80px; }
.header { background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%); color: #fff; padding: 40px 20px; text-align: center; position: relative; overflow: hidden; }
.header::after { content:""; position: absolute; top: -50%; right: -20%; width: 300px; height: 300px; background: rgba(201,168,76,0.1); border-radius: 50%; }
.header h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; position: relative; z-index: 1; }
.header .subtitle { font-size: 15px; opacity: 0.85; position: relative; z-index: 1; }
.header .badge { display: inline-block; background: var(--gold); color: var(--primary); padding: 4px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; margin-top: 12px; position: relative; z-index: 1; }
.container { max-width: 720px; margin: 0 auto; padding: 20px; }
.intro-card { background: var(--card); border-radius: 12px; padding: 28px; margin: 24px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.08); border-left: 4px solid var(--primary); }
.intro-card h3 { font-size: 17px; color: var(--primary); margin-bottom: 8px; }
.intro-card p { font-size: 14px; color: var(--text-light); margin-bottom: 6px; }
.dim-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 20px 0; position: sticky; top: 10px; z-index: 10; background: var(--bg); padding: 12px 0; }
.dim-nav button { padding: 8px 16px; border: 2px solid var(--border); border-radius: 20px; background: var(--card); cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.2s; color: var(--text); }
.dim-nav button:hover { border-color: var(--primary-light); }
.dim-nav button.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.dim-nav button.done { border-color: var(--success); color: var(--success); }
.dim-nav button.done::after { content: " ✓"; }
.section { background: var(--card); border-radius: 12px; padding: 32px 28px; margin: 20px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.section h2 { font-size: 22px; margin-bottom: 4px; display: flex; align-items: center; gap: 10px; }
.section .dim-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 80px; height: 36px; border-radius: 20px; font-weight: 700; font-size: 16px; color: #fff; padding: 0 14px; flex-shrink: 0; }
.section .dim-desc { font-size: 14px; color: var(--text-light); margin: 8px 0 20px; }
.question { padding: 16px 0; border-bottom: 1px solid var(--border); }
.question:last-child { border-bottom: none; }
.question .q-num { font-weight: 700; color: var(--primary); margin-right: 6px; font-size: 14px; }
.question .q-text { font-size: 15px; font-weight: 600; margin-bottom: 10px; }
.choice-group { display: flex; flex-direction: column; gap: 6px; }
.choice { display: flex; align-items: center; padding: 10px 14px; border: 2px solid var(--border); border-radius: 8px; cursor: pointer; transition: all 0.15s; gap: 10px; }
.choice:hover { border-color: var(--primary-light); background: #f8fafc; }
.choice.selected-a { border-color: #3182ce; background: #ebf8ff; }
.choice.selected-b { border-color: #dd6b20; background: #fffaf0; }
.choice .radio { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--border); display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 12px; }
.choice.selected-a .radio { border-color: #3182ce; background: #3182ce; color: #fff; }
.choice.selected-b .radio { border-color: #dd6b20; background: #dd6b20; color: #fff; }
.choice .opt-label { font-size: 14px; line-height: 1.5; }
.progress-wrap { background: var(--card); border-radius: 12px; padding: 20px 28px; margin: 20px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.progress-bar { height: 8px; background: var(--border); border-radius: 4px; overflow: hidden; margin-bottom: 8px; }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--primary-light), var(--gold)); border-radius: 4px; transition: width 0.3s; }
.progress-text { font-size: 14px; color: var(--text-light); text-align: center; }
.results { background: var(--card); border-radius: 12px; padding: 32px 28px; margin: 20px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.08); display: none; overflow: hidden; }
.results.show { display: block; overflow: visible; }
.results h2 { font-size: 24px; color: var(--primary); text-align: center; margin-bottom: 24px; }
.result-type { text-align: center; margin: 20px 0; }
.result-type .type-letters { font-size: 56px; font-weight: 900; letter-spacing: 6px; background: linear-gradient(135deg, var(--primary), var(--gold)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.result-type .type-label { font-size: 15px; color: var(--text-light); margin-top: 4px; }
.dim-results { margin: 24px 0; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 500px) { .dim-results { grid-template-columns: 1fr; } }
.dim-card { padding: 16px; border-radius: 8px; text-align: center; border: 2px solid var(--border); }
.dim-card .dim-name { font-size: 12px; color: var(--text-light); text-transform: uppercase; letter-spacing: 2px; }
.dim-card .dim-result { font-size: 28px; font-weight: 900; margin: 6px 0; }
.dim-card .dim-bar-wrap { height: 6px; background: #f0f0f0; border-radius: 3px; margin-top: 8px; overflow: hidden; }
.dim-card .dim-bar-fill { height: 100%; border-radius: 3px; transition: width 1s ease-out; }
.interpretation { margin: 24px 0; padding: 20px; background: var(--gold-light); border-radius: 8px; border-left: 4px solid var(--gold); }
.interpretation h4 { font-size: 16px; color: var(--primary); margin-bottom: 8px; }
.interpretation p { font-size: 14px; color: var(--text); line-height: 1.7; margin-bottom: 6px; }
.type-traits { margin: 20px 0; padding: 20px; background: #f0f4ff; border-radius: 8px; }
.type-traits h4 { font-size: 15px; color: var(--primary); margin-bottom: 8px; }
.type-traits ul { padding-left: 20px; font-size: 14px; }
.type-traits li { margin: 4px 0; }
.btn-primary { display: block; margin: 24px auto 0; padding: 14px 40px; background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: #fff; border: none; border-radius: 8px; font-size: 16px; font-weight: 700; cursor: pointer; transition: all 0.2s; }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(26,58,92,0.3); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.warning-box { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 16px 20px; margin: 20px 0; font-size: 14px; color: #856404; }
.warning-box strong { color: #664d03; }
.footer { text-align: center; padding: 40px 20px; color: var(--text-light); font-size: 13px; }
/* History Panel */
.history-panel { background: var(--card); border-radius: 12px; padding: 28px; margin: 20px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.history-panel h3 { font-size: 18px; color: var(--primary); margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.history-list { display: flex; flex-direction: column; gap: 8px; }
.history-item { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border: 2px solid var(--border); border-radius: 8px; cursor: pointer; transition: all 0.15s; gap: 12px; flex-wrap: wrap; }
.history-item:hover { border-color: var(--primary-light); background: #f8fafc; }
.history-item.active { border-color: var(--primary); background: #ebf4ff; }
.history-item .hi-type { font-size: 20px; font-weight: 900; color: var(--primary); letter-spacing: 3px; }
.history-item .hi-name { font-size: 14px; color: var(--text-light); }
.history-item .hi-time { font-size: 13px; color: var(--text-light); }
.history-item .hi-current { font-size: 12px; color: var(--success); font-weight: 600; }
.history-empty { text-align: center; color: var(--text-light); font-size: 14px; padding: 24px 0; }
.hi-delete { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: 50%; background: transparent; color: var(--text-light); font-size: 14px; cursor: pointer; transition: all 0.15s; flex-shrink: 0; margin-left: auto; }
.hi-delete:hover { background: #fee2e2; color: #dc2626; }
.history-footer { text-align: center; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }
.btn-clear-history { display: inline-block; padding: 8px 24px; background: #fff; color: #dc2626; border: 1px solid #fecaca; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.2s; }
.btn-clear-history:hover { background: #fef2f2; border-color: #dc2626; }
.btn-reset { display: inline-block; padding: 10px 28px; background: #fff; color: var(--primary); border: 2px solid var(--primary); border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.btn-reset:hover { background: var(--primary); color: #fff; }
.result-timestamp { text-align: center; font-size: 13px; color: var(--text-light); margin-top: -16px; margin-bottom: 16px; }
.btn-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 24px; }
.bg-ei { background: #f3e8ff; border-color: var(--ei-color); }
.bg-sn { background: #e8f0ff; border-color: var(--sn-color); }
.bg-tf { background: #fff4e8; border-color: var(--tf-color); }
.bg-jp { background: #e8fff0; border-color: var(--jp-color); }
</style>
</head>
<body>
<div class="header">
<h1>🧠 MBTI 性格测试</h1>
<p class="subtitle">Myers-Briggs Type Indicator · 自评版 · 28题</p>
<span class="badge">全程约8分钟</span>
<span class="badge" style="background:#1a3a5c;color:#c9a84c;border:1px solid #c9a84c;margin-left:6px;">👤 天恒专属</span>
</div>
<div class="container">
<div class="intro-card">
<h3>📖 这是什么?</h3>
<p>MBTI基于荣格心理学理论,把人的性格分成<strong>4个维度</strong>,组合成16种类型。它不判断"好坏",只描述"你是怎么样的人"。</p>
</div>
<div class="intro-card">
<h3>📝 怎么填?</h3>
<p>下面有28道题,每题有 <strong>A</strong><strong>B</strong> 两个选项。请选择<strong>更接近你真实感受</strong>的那个——不是"应该选哪个",而是<strong>"你实际上更偏向哪边"</strong></p>
<p style="margin-top:8px;">💡 如果两个都不完全符合,选<strong>相对更贴切</strong>的那个。尽量不要跳题。</p>
</div>
<div class="intro-card" style="border-left-color: var(--gold); background: var(--gold-light);">
<h3>4个维度速览</h3>
<table style="width:100%;font-size:14px;border-collapse:collapse;">
<tr><td style="padding:6px 0;"><strong>E 外向 vs I 内向</strong></td><td>你从哪获得能量?社交充电 vs 独处充电</td></tr>
<tr><td style="padding:6px 0;"><strong>S 实感 vs N 直觉</strong></td><td>你如何接收信息?相信具体事实 vs 相信直觉规律</td></tr>
<tr><td style="padding:6px 0;"><strong>T 思考 vs F 情感</strong></td><td>你如何做决定?靠逻辑分析 vs 靠价值观和感受</td></tr>
<tr><td style="padding:6px 0;"><strong>J 判断 vs P 感知</strong></td><td>你如何安排生活?喜欢计划有序 vs 喜欢灵活开放</td></tr>
</table>
</div>
<div id="dimNav" class="dim-nav" style="display:none;"></div>
<div class="progress-wrap">
<div class="progress-bar"><div id="progressFill" class="progress-fill" style="width:0%"></div></div>
<div class="progress-text" id="progressText">已答 0 / 28 题</div>
</div>
<div id="questions"></div>
<div class="section" id="submitSection" style="text-align:center;">
<button id="btnSubmit" class="btn-primary" disabled onclick="calculateResults()" style="margin-top:0;">📊 查看结果</button>
</div>
<div id="results" class="results">
<h2>🎯 你的MBTI性格类型</h2>
<div class="result-timestamp" id="resultTimestamp"></div>
<div class="result-type">
<div class="type-letters" id="resultType"></div>
<div class="type-label">四字母代码 · Personality Type</div>
</div>
<div id="dimDetails" class="dim-results"></div>
<div id="interpretation" class="interpretation"></div>
<div class="btn-actions">
<button class="btn-reset" onclick="resetTest()">🔄 再做一次</button>
<button class="btn-reset" onclick="submitToServer()" style="border-color:var(--success);color:var(--success);">📤 提交结果到服务器</button>
<button class="btn-reset" onclick="exportCurrentResult()" style="border-color:var(--sn-color);color:var(--sn-color);">📥 导出当前结果</button>
<button class="btn-reset" onclick="exportAllHistory()" style="border-color:var(--ei-color);color:var(--ei-color);">📦 导出全部历史</button>
</div>
<div class="warning-box">
<strong>⚠️ 重要说明:</strong>本测试为自评简化版,用于快速了解性格倾向。MBTI类型会随年龄和经历变化,结果仅作参考,不应作为唯一决策依据。完整93题的科学测评建议配合专业认证施测师。
</div>
</div>
<div id="historyPanel" class="history-panel" style="display:none;">
<h3>📋 测试历史记录</h3>
<div id="historyList" class="history-list"></div>
</div>
</div>
<div class="footer">
<p>基于 Myers-Briggs Type Indicator 理论编制 | 天恒专属 · 高考志愿规划工具</p>
</div>
<script>
// ========== MBTI Test Data ==========
const dimensions = [
{
id: 'ei', codeA: 'E', codeB: 'I',
name: '外向 E vs 内向 I',
fullName: 'Extraversion vs Introversion',
desc: '你从哪获得能量?',
color: '#805ad5',
bgClass: 'bg-ei',
items: [
{ a: '和一群朋友聚会时,我精力充沛、越玩越嗨', b: '我更享受独处或和一二知己深聊,人太多反而消耗' },
{ a: '我喜欢先说出来再慢慢理清思路', b: '我喜欢先想清楚再开口,说出来的都是想好的' },
{ a: '认识新朋友让我兴奋', b: '认识新朋友让我有点紧张' },
{ a: '我更喜欢热闹活跃的环境', b: '我更喜欢安静、不被打扰的环境' },
{ a: '在群体中我倾向于主动发起对话', b: '在群体中我倾向于等别人先开口' },
{ a: '我的社交圈比较广,不同类型的朋友都有', b: '我有少数几个深交的朋友,已经足够' },
{ a: '参加完集体活动后我精神焕发', b: '参加完集体活动后我需要独处恢复能量' }
]
},
{
id: 'sn', codeA: 'S', codeB: 'N',
name: '实感 S vs 直觉 N',
fullName: 'Sensing vs Intuition',
desc: '你如何接收和理解信息?',
color: '#3182ce',
bgClass: 'bg-sn',
items: [
{ a: '我更关注具体的细节和事实数据', b: '我更关注整体图景和未来可能性' },
{ a: '我喜欢按部就班、有章可循的方法', b: '我喜欢尝试新方法、新思路,哪怕不一定成功' },
{ a: '我相信眼见为实,需要看到证据', b: '我相信直觉和预感,哪怕暂时说不清为什么' },
{ a: '我更看重实用的技能和技术', b: '我更着迷于抽象的理论和概念' },
{ a: '我更多关注当下和过往的经验', b: '我更多关注未来和可能性' },
{ a: '我喜欢有明确步骤和流程的任务', b: '我喜欢自由发挥和探索的任务' },
{ a: '做熟悉的事让我感到踏实', b: '不断尝试新事物才让我觉得有意思' }
]
},
{
id: 'tf', codeA: 'T', codeB: 'F',
name: '思考 T vs 情感 F',
fullName: 'Thinking vs Feeling',
desc: '你如何做决定和判断?',
color: '#dd6b20',
bgClass: 'bg-tf',
items: [
{ a: '做决定时我更看重逻辑分析和客观事实', b: '做决定时我更看重价值观和对他人的影响' },
{ a: '我认为公平公正比照顾个人感受更重要', b: '我认为理解和体谅比死守规则更重要' },
{ a: '别人常说我比较理性、冷静', b: '别人常说我比较感性、有同理心' },
{ a: '发现问题时我会直接指出来', b: '指出问题前我会先考虑会不会伤到对方' },
{ a: '我更看重效率和结果', b: '我更看重过程和人际关系的和谐' },
{ a: '给别人提意见或批评对我来说不太难', b: '批评别人让我感到很不舒服' },
{ a: '我认为规则既然定了就该严格执行', b: '我认为规则可以根据实际情况灵活处理' }
]
},
{
id: 'jp', codeA: 'J', codeB: 'P',
name: '判断 J vs 感知 P',
fullName: 'Judging vs Perceiving',
desc: '你如何安排生活和应对外部世界?',
color: '#38a169',
bgClass: 'bg-jp',
items: [
{ a: '我喜欢提前计划,什么事都安排好', b: '我喜欢随遇而安,灵活应对变化' },
{ a: '提前完成任务让我感到轻松和满足', b: '截止日期前的压力反而让我效率最高' },
{ a: '我喜欢井井有条、有规律的生活', b: '我享受即兴发挥和不被安排的自由' },
{ a: '做出决定后我感觉如释重负', b: '我喜欢保持选择开放,不急着做决定' },
{ a: '出去玩之前我喜欢做好攻略和计划', b: '走哪算哪,随性探索更有趣' },
{ a: '我喜欢先把该做的事做完再放松', b: '有时候先放松一下再做事效率更高' },
{ a: '有规律的作息让我状态稳定', b: '我根据状态和心情来安排时间' }
]
}
];
// ========== State ==========
const answers = {}; // { 'ei0': 'A', 'ei1': 'B', ... }
dimensions.forEach(d => { for (let i = 0; i < 7; i++) answers[d.id + i] = null; });
// ========== Render ==========
function renderAll() {
renderNav();
renderQuestions();
updateProgress();
}
function renderNav() {
const nav = document.getElementById('dimNav');
nav.innerHTML = dimensions.map(d => {
const answered = getDimAnswered(d.id);
const allDone = answered === 7;
return `<button class="${allDone ? 'done' : ''}" onclick="scrollToDim('${d.id}')" style="border-color:${d.color};">
${d.name}
</button>`;
}).join('');
nav.style.display = 'flex';
}
function renderQuestions() {
const container = document.getElementById('questions');
container.innerHTML = dimensions.map(d => `
<div class="section ${d.bgClass}" id="dim-${d.id}" style="border-top: 3px solid ${d.color};">
<h2>
<span class="dim-badge" style="background:${d.color};">${d.name}</span>
</h2>
<p class="dim-desc">${d.desc} 共7题,每题选A或B</p>
${d.items.map((item, i) => {
const key = d.id + i;
const val = answers[key];
return `<div class="question">
<div class="q-text"><span class="q-num">${i+1}.</span>请选择更接近你的选项:</div>
<div class="choice-group">
<div class="choice ${val==='A'?'selected-a':''}" onclick="setAnswer('${key}','A')" id="choice-${key}-A">
<div class="radio">${val==='A'?'✓':''}</div>
<span class="opt-label"><strong>A.</strong> ${item.a}</span>
</div>
<div class="choice ${val==='B'?'selected-b':''}" onclick="setAnswer('${key}','B')" id="choice-${key}-B">
<div class="radio">${val==='B'?'✓':''}</div>
<span class="opt-label"><strong>B.</strong> ${item.b}</span>
</div>
</div>
</div>`;
}).join('')}
</div>
`).join('');
}
function setAnswer(key, val) {
answers[key] = val;
// Update choice styles
const dimId = key.slice(0, 2);
const idx = parseInt(key.slice(2));
const choiceA = document.getElementById('choice-' + key + '-A');
const choiceB = document.getElementById('choice-' + key + '-B');
if (choiceA) choiceA.className = 'choice ' + (val === 'A' ? 'selected-a' : '');
if (choiceB) choiceB.className = 'choice ' + (val === 'B' ? 'selected-b' : '');
updateProgress();
renderNav();
}
function getDimAnswered(id) {
let count = 0;
for (let i = 0; i < 7; i++) if (answers[id + i] !== null) count++;
return count;
}
function updateProgress() {
let total = 0, answered = 0;
dimensions.forEach(d => {
for (let i = 0; i < 7; i++) { total++; if (answers[d.id + i] !== null) answered++; }
});
const pct = Math.round((answered / total) * 100);
document.getElementById('progressFill').style.width = pct + '%';
document.getElementById('progressText').textContent = `已答 ${answered} / ${total} 题 (${pct}%)`;
document.getElementById('btnSubmit').disabled = answered < total;
}
function scrollToDim(id) {
document.getElementById('dim-' + id).scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// ========== Calculate & Display ==========
function calculateResults() {
// Compute scores for each dichotomy
const dimResults = dimensions.map(d => {
let countA = 0;
for (let i = 0; i < 7; i++) if (answers[d.id + i] === 'A') countA++;
const scoreA = countA;
const scoreB = 7 - countA;
const result = countA >= 4 ? d.codeA : d.codeB;
const pctA = Math.round((countA / 7) * 100);
const certainty = (countA === 3 || countA === 4) ? '(边界型,两种倾向接近)' : '';
return {
id: d.id, name: d.name, codeA: d.codeA, codeB: d.codeB, result, scoreA, scoreB, pctA,
color: d.color, certainty, bgClass: d.bgClass
};
});
// Full MBTI type
const mbtiType = dimResults.map(d => d.result).join('');
// Generate timestamp
const now = new Date();
const timestamp = now.toLocaleString('zh-CN', { year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false });
// Display result type & timestamp
document.getElementById('resultType').textContent = mbtiType;
document.getElementById('resultTimestamp').textContent = '⏰ 测试时间:' + timestamp;
displayMBTIResult(mbtiType, dimResults);
// Show results
document.getElementById('results').classList.add('show');
document.getElementById('results').scrollIntoView({ behavior: 'smooth', block: 'start' });
// Save result to localStorage
const resultData = { type: mbtiType, details: dimResults, timestamp: now.toISOString(), timestampFormatted: timestamp };
saveResult(resultData);
renderHistory();
}
function displayMBTIResult(mbtiType, dimResults) {
// Dimension detail cards
document.getElementById('dimDetails').innerHTML = dimResults.map(d => `
<div class="dim-card ${d.bgClass}">
<div class="dim-name">${d.name}</div>
<div class="dim-result" style="color:${d.color};">${d.result}</div>
<div style="font-size:12px;color:var(--text-light);">
${d.codeA} ${d.scoreA}/7 ← → ${d.codeB} ${d.scoreB}/7
${d.certainty ? '<br>'+d.certainty : ''}
</div>
<div class="dim-bar-wrap">
<div class="dim-bar-fill" style="width:${d.pctA}%;background:${d.color};border-radius:3px 0 0 3px;"></div>
</div>
</div>
`).join('');
const typeDescriptions = {
'INTJ': { name: '建筑师', traits: ['善于独立思考', '有战略眼光', '追求完美', '喜欢规划和系统'], study: '科研、工程、计算机、法学、经济学', note: '适合需要深度思考和独立研究的专业' },
'INTP': { name: '逻辑学家', traits: ['好奇心极强', '善于分析', '喜欢理论探索', '不拘常规'], study: '数学、物理、计算机科学、哲学、心理学', note: '适合需要创新和理论深度的专业' },
'ENTJ': { name: '指挥官', traits: ['天生领导者', '果断自信', '目标驱动', '善于组织'], study: '管理学、经济学、法学、工程管理、政治学', note: '适合需要领导力和战略眼光的专业' },
'ENTP': { name: '辩论家', traits: ['思维敏捷', '喜欢挑战', '善于创新', '不喜欢重复'], study: '法学、新闻学、市场营销、计算机、设计学', note: '适合需要创造力和快速应变的专业' },
'INFJ': { name: '提倡者', traits: ['理想主义', '洞察力强', '关心他人', '有使命感'], study: '心理学、教育学、医学、社会工作、文学', note: '适合需要深度共情和价值感的专业' },
'INFP': { name: '调停者', traits: ['内心丰富', '忠于价值观', '富有创造力', '追求意义'], study: '文学、艺术学、心理学、哲学、设计学', note: '适合需要自我表达和意义感的专业' },
'ENFJ': { name: '主人公', traits: ['魅力十足', '善解人意', '激励他人', '有责任感'], study: '教育学、人力资源管理、新闻传播、医学、公共管理', note: '适合需要感染力和组织力的专业' },
'ENFP': { name: '竞选者', traits: ['热情洋溢', '想象力丰富', '善于社交', '追求新鲜感'], study: '新闻学、市场营销、设计学、心理学、文学', note: '适合需要创意和人际互动的专业' },
'ISTJ': { name: '物流师', traits: ['务实可靠', '做事有条理', '遵守规则', '注重细节'], study: '会计学、审计学、工程管理、行政管理、法学', note: '适合需要严谨和规范的专业' },
'ISFJ': { name: '守卫者', traits: ['细心周到', '有奉献精神', '踏实可靠', '重视传统'], study: '医学、护理学、教育学、社会工作、档案学', note: '适合需要耐心和细致的服务型专业' },
'ESTJ': { name: '总经理', traits: ['执行力强', '组织有序', '务实高效', '喜欢管理'], study: '管理学、会计学、工程管理、法学、军事学', note: '适合需要执行力和管理能力的专业' },
'ESFJ': { name: '执政官', traits: ['热心周到', '善于合作', '注重和谐', '乐于助人'], study: '教育学、护理学、人力资源、酒店管理、社会工作', note: '适合需要人际协调和服务的专业' },
'ISTP': { name: '鉴赏家', traits: ['动手能力强', '冷静务实', '喜欢探索原理', '不拘小节'], study: '机械工程、计算机科学、电子信息、体育学、工艺美术', note: '适合需要实操和解决问题的专业' },
'ISFP': { name: '探险家', traits: ['审美敏锐', '温和友善', '活在当下', '不喜欢冲突'], study: '设计学、艺术学、音乐学、园艺、护理学', note: '适合需要审美和动手创作的专业' },
'ESTP': { name: '企业家', traits: ['行动派', '适应力强', '喜欢冒险', '善于应变'], study: '市场营销、体育学、管理学、法学、创业学', note: '适合需要快速反应和实战能力的专业' },
'ESFP': { name: '表演者', traits: ['活力四射', '社交达人', '享受当下', '乐于助人'], study: '表演艺术、旅游管理、新闻传播、社会工作、体育教育', note: '适合需要表现力和人际互动的专业' }
};
const t = typeDescriptions[mbtiType] || { name: '', traits: [], study: '', note: '' };
const ei = dimResults[0], sn = dimResults[1], tf = dimResults[2], jp = dimResults[3];
document.getElementById('interpretation').innerHTML = `
<h4>📌 你的MBTI类型:${mbtiType}${t.name}</h4>
<p><strong>性格特征:</strong>${t.traits.join('、')}</p>
<p><strong>推荐专业方向:</strong>${t.study}</p>
<p><strong>选择建议:</strong>${t.note}</p>
<hr style="margin:16px 0;border-color:#ddd;">
<p style="font-size:13px;color:var(--text-light);"><strong>你的4个维度详细偏好:</strong></p>
<p style="font-size:13px;color:var(--text-light);">${ei.codeA}/${ei.codeB}(${ei.scoreA}/${ei.scoreB}) → <strong>${ei.result}</strong> · ${sn.codeA}/${sn.codeB}(${sn.scoreA}/${sn.scoreB}) → <strong>${sn.result}</strong> · ${tf.codeA}/${tf.codeB}(${tf.scoreA}/${tf.scoreB}) → <strong>${tf.result}</strong> · ${jp.codeA}/${jp.codeB}(${jp.scoreA}/${jp.scoreB}) → <strong>${jp.result}</strong></p>
<p style="font-size:13px;color:var(--text-light);margin-top:4px;">${dimResults.filter(d=>d.certainty).map(d=>'<strong>'+d.name.split(' ')[0]+'</strong>处于边界值').join('') || '所有维度倾向均较明确'}</p>
`;
}
// ========== localStorage Persistence ==========
const STORAGE_KEY = 'mbti_test_history';
function saveResult(resultData) {
let history = [];
try {
history = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
} catch(e) { history = []; }
history.push(resultData);
// Keep max 20 entries
if (history.length > 20) history = history.slice(-20);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
} catch(e) { /* quota exceeded, silently fail */ }
}
function loadHistory() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
} catch(e) { return []; }
}
function renderHistory() {
const history = loadHistory();
const panel = document.getElementById('historyPanel');
const list = document.getElementById('historyList');
if (history.length === 0) {
list.innerHTML = '<div class="history-empty">暂无历史记录,完成一次测试后会自动保存</div>';
} else {
const typeDescriptions = {
'INTJ': '建筑师','INTP': '逻辑学家','ENTJ': '指挥官','ENTP': '辩论家',
'INFJ': '提倡者','INFP': '调停者','ENFJ': '主人公','ENFP': '竞选者',
'ISTJ': '物流师','ISFJ': '守卫者','ESTJ': '总经理','ESFJ': '执政官',
'ISTP': '鉴赏家','ISFP': '探险家','ESTP': '企业家','ESFP': '表演者'
};
list.innerHTML = history.map((h, idx) => {
const isLatest = idx === history.length - 1;
return `<div class="history-item${isLatest ? ' active' : ''}" onclick="showHistoryResult(${idx})">
<span class="hi-type">${h.type}</span>
<span class="hi-name">${typeDescriptions[h.type] || ''}</span>
<span class="hi-time">${h.timestampFormatted}</span>
${isLatest ? '<span class="hi-current">最新</span>' : ''}
<button class="hi-delete" title="删除此条记录" onclick="event.stopPropagation();deleteHistoryItem(${idx});">✕</button>
</div>`;
}).reverse().join(''); // newest first
list.innerHTML += '<div class="history-footer"><button class="btn-clear-history" onclick="clearAllHistory()">🗑️ 清空全部历史</button></div>';
}
panel.style.display = 'block';
}
function deleteHistoryItem(idx) {
if (!confirm('确定删除这条测试记录吗?')) return;
let history = loadHistory();
if (idx < 0 || idx >= history.length) return;
history.splice(idx, 1);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
} catch(e) { /* quota exceeded, silently fail */ }
renderHistory();
// If no history left, hide the panel
if (history.length === 0) {
document.getElementById('historyPanel').style.display = 'none';
}
}
function clearAllHistory() {
if (!confirm('确定清空全部测试历史记录吗?此操作不可恢复。')) return;
try {
localStorage.removeItem(STORAGE_KEY);
} catch(e) { /* silently fail */ }
document.getElementById('historyPanel').style.display = 'none';
document.getElementById('historyList').innerHTML = '';
}
function showHistoryResult(idx) {
const history = loadHistory();
if (idx < 0 || idx >= history.length) return;
const h = history[idx];
// Highlight active item
document.querySelectorAll('.history-item').forEach((el, i) => {
el.classList.toggle('active', i === (history.length - 1 - idx)); // reversed display
});
// Re-display result
document.getElementById('resultType').textContent = h.type;
document.getElementById('resultTimestamp').textContent = '⏰ 测试时间:' + h.timestampFormatted;
displayMBTIResult(h.type, h.details);
document.getElementById('results').classList.add('show');
document.getElementById('results').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function resetTest() {
if (!confirm('确定要重新测试吗?当前结果已自动保存到历史记录中,不会丢失。')) return;
// Reset all answers
dimensions.forEach(d => { for (let i = 0; i < 7; i++) answers[d.id + i] = null; });
// Hide results
document.getElementById('results').classList.remove('show');
document.getElementById('resultType').textContent = '';
document.getElementById('resultTimestamp').textContent = '';
document.getElementById('dimDetails').innerHTML = '';
document.getElementById('interpretation').innerHTML = '';
// Re-render questions
renderAll();
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// ========== 提交到服务器 ==========
async function submitToServer() {
const type = document.getElementById('resultType').textContent;
if (!type) { alert('请先完成测试再提交'); return; }
const dimResults = dimensions.map(d => {
let countA = 0;
for (let i = 0; i < 7; i++) if (answers[d.id + i] === 'A') countA++;
return {
id: d.id, name: d.name, codeA: d.codeA, codeB: d.codeB,
result: countA >= 4 ? d.codeA : d.codeB,
scoreA: countA, scoreB: 7 - countA
};
});
try {
const resp = await fetch('/api/standalone/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
test_name: 'mbti',
user_name: '天恒',
result: type,
full_data: { type, dimResults }
})
});
const result = await resp.json();
if (result.success) {
alert('✅ MBTI测试结果已成功提交到服务器!\n可在首页「问卷完成情况」查看。');
} else {
alert('提交失败:' + (result.error || '未知错误'));
}
} catch(e) {
alert('提交失败:无法连接服务器。请确保通过 NAS 地址访问本页面。');
}
}
// ========== Export ==========
function exportCurrentResult() {
const history = loadHistory();
if (history.length === 0) { alert('暂无测试结果可导出'); return; }
const last = history[history.length - 1];
const dateStr = new Date().toISOString().slice(0, 10);
const blob = new Blob([JSON.stringify(last, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `MBTI测试结果_${dateStr}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function exportAllHistory() {
const history = loadHistory();
if (history.length === 0) { alert('暂无历史记录可导出'); return; }
const dateStr = new Date().toISOString().slice(0, 10);
const blob = new Blob([JSON.stringify(history, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `MBTI测试全部历史_${dateStr}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ========== Init ==========
renderAll();
// Load and show history on page load if exists
(function() {
const history = loadHistory();
if (history.length > 0) {
renderHistory();
}
})();
</script>
</body>
</html>