feat: 完善中文心理测评平台
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user