198 lines
6.2 KiB
TypeScript
198 lines
6.2 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
calculateBigFiveResults,
|
|
calculateIpipNeoResults,
|
|
ipipNeoItemsByVersion,
|
|
} from '../../test/private/BigFiveCalculator';
|
|
import { ipipNeoFacets } from '@/questionairies/bigfive/neo-data';
|
|
|
|
interface BigFiveResultProps {
|
|
answers: string[];
|
|
version?: 50 | 120 | 300;
|
|
}
|
|
|
|
interface ScoreResult {
|
|
score: number;
|
|
average: number;
|
|
itemCount?: number;
|
|
}
|
|
|
|
const domainLabels = {
|
|
neuroticism: {
|
|
name: '神经质',
|
|
desc: '情绪敏感、压力反应以及体验焦虑、烦躁等负面情绪的倾向。',
|
|
},
|
|
extraversion: {
|
|
name: '外向性',
|
|
desc: '社交投入、自信表达、活跃度和积极情绪。',
|
|
},
|
|
openness: {
|
|
name: '开放性',
|
|
desc: '想象力、审美、求知、尝新和价值观开放程度。',
|
|
},
|
|
agreeableness: {
|
|
name: '宜人性',
|
|
desc: '信任、真诚、合作、利他、谦逊和同情倾向。',
|
|
},
|
|
conscientiousness: {
|
|
name: '尽责性',
|
|
desc: '自我效能、条理、责任、自律、成就追求和谨慎。',
|
|
},
|
|
};
|
|
|
|
const shortLabels = {
|
|
extraversion: domainLabels.extraversion,
|
|
agreeableness: domainLabels.agreeableness,
|
|
conscientiousness: domainLabels.conscientiousness,
|
|
emotionalStability: {
|
|
name: '情绪稳定性',
|
|
desc: '情绪平稳、压力耐受和较少焦虑烦躁。',
|
|
},
|
|
openness: domainLabels.openness,
|
|
};
|
|
|
|
function level(average: number) {
|
|
if (average >= 3.8) return '较高';
|
|
if (average <= 2.4) return '较低';
|
|
return '中等';
|
|
}
|
|
|
|
function barWidth(average: number) {
|
|
return `${Math.max(0, Math.min(100, ((average - 1) / 4) * 100))}%`;
|
|
}
|
|
|
|
function ScoreCard({
|
|
name,
|
|
desc,
|
|
result,
|
|
maxScore,
|
|
}: {
|
|
name: string;
|
|
desc: string;
|
|
result: ScoreResult;
|
|
maxScore: number;
|
|
}) {
|
|
return (
|
|
<div className="border rounded-lg p-5 bg-white shadow-sm">
|
|
<div className="flex items-start justify-between gap-4 mb-3">
|
|
<div>
|
|
<h4 className="font-semibold">{name}</h4>
|
|
<p className="text-sm text-muted-foreground mt-1">{desc}</p>
|
|
</div>
|
|
<div className="text-right shrink-0">
|
|
<div className="text-2xl font-semibold text-indigo-600">
|
|
{result.score}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">/ {maxScore}</div>
|
|
</div>
|
|
</div>
|
|
<div className="h-2 rounded-full bg-gray-100 overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full bg-indigo-500"
|
|
style={{ width: barWidth(result.average) }}
|
|
/>
|
|
</div>
|
|
<div className="mt-3 text-sm">
|
|
倾向:<span className="font-medium">{level(result.average)}</span>
|
|
<span className="text-muted-foreground ml-2">
|
|
平均 {result.average.toFixed(2)} / 5
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BigFiveResult({ answers, version = 50 }: BigFiveResultProps) {
|
|
if (version === 50) {
|
|
const results = calculateBigFiveResults(answers);
|
|
|
|
return (
|
|
<div className="mt-6 space-y-6">
|
|
<div className="bg-white border rounded-lg p-6 shadow-sm">
|
|
<h3 className="text-lg font-semibold mb-2">五大人格结果</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
每个维度满分50分。分数表示相对倾向,没有绝对好坏。
|
|
</p>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{Object.entries(shortLabels).map(([key, info]) => (
|
|
<ScoreCard
|
|
key={key}
|
|
name={info.name}
|
|
desc={info.desc}
|
|
result={results[key] as ScoreResult}
|
|
maxScore={50}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const items = ipipNeoItemsByVersion[version];
|
|
const results = calculateIpipNeoResults(answers, [...items]);
|
|
const domainItemCount = version === 120 ? 24 : 60;
|
|
const facetItemCount = version === 120 ? 4 : 10;
|
|
|
|
return (
|
|
<div className="mt-6 space-y-8">
|
|
<div className="bg-white border rounded-lg p-6 shadow-sm">
|
|
<h3 className="text-lg font-semibold mb-2">
|
|
IPIP-NEO {version}题结果
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
先看五大维度了解整体轮廓,再查看30个面向理解具体差异。分数代表相对倾向,没有绝对好坏。
|
|
</p>
|
|
</div>
|
|
|
|
<section>
|
|
<h3 className="text-lg font-semibold mb-4">五大维度</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{Object.entries(domainLabels).map(([key, info]) => (
|
|
<ScoreCard
|
|
key={key}
|
|
name={info.name}
|
|
desc={info.desc}
|
|
result={results.domains[key] as ScoreResult}
|
|
maxScore={domainItemCount * 5}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h3 className="text-lg font-semibold mb-4">30个细分面向</h3>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
{Object.entries(ipipNeoFacets).map(([key, facet]) => {
|
|
const result = results.facets[key] as ScoreResult;
|
|
return (
|
|
<div key={key} className="border rounded-lg bg-white p-4">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<h4 className="font-medium">{facet.name}</h4>
|
|
<span className="text-sm font-semibold text-indigo-600">
|
|
{result.score} / {facetItemCount * 5}
|
|
</span>
|
|
</div>
|
|
<div className="mt-3 h-1.5 rounded-full bg-gray-100 overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full bg-emerald-500"
|
|
style={{ width: barWidth(result.average) }}
|
|
/>
|
|
</div>
|
|
<div className="mt-2 text-xs text-muted-foreground">
|
|
{level(result.average)} · 平均 {result.average.toFixed(2)} / 5
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
|
|
<div className="bg-gray-50 border rounded-lg p-4 text-sm text-gray-700">
|
|
建议结合维度和面向一起阅读:同一大维度下的六个面向可能高低不同,这通常比单一总分更能描述个人风格。
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|