feat: 升级结构化计分可信度

This commit is contained in:
2026-06-23 02:21:09 +02:00
parent e3825c5a4e
commit ca77aa0896
10 changed files with 705 additions and 139 deletions
@@ -199,14 +199,17 @@ export function Questionnaire({
answer: option?.content || value,
};
});
const scoreSummary = buildScoreSummary(id, resultAnswers);
const record = await addAssessmentRecord({
profileId: profile.id,
questionnaireId: id,
questionnaireTitle: questionnaire.title,
questionnaireVersion: scoreSummary.questionnaireVersion,
scoreVersion: scoreSummary.scoreVersion,
category: questionnaire.category,
completedAt,
answers: recordedAnswers,
scoreSummary: buildScoreSummary(id, resultAnswers),
scoreSummary,
retestSuitable: questionnaire.evaluation?.retestSuitable,
recommendedInterval: questionnaire.evaluation?.recommendedInterval,
});
+39 -3
View File
@@ -32,6 +32,41 @@ function displayDate(value: string) {
return new Date(value).toLocaleString('zh-CN', { hour12: false });
}
function directionText(direction?: ScoreMetric['direction']) {
switch (direction) {
case 'risk':
return '高分代表风险或困扰升高';
case 'protective':
return '高分代表改善或保护因素更高';
case 'ability':
return '高分代表表现更好';
case 'trait':
return '高分代表该特质更明显';
case 'mixed':
return '高分含义需按维度查看';
default:
return '高分含义需结合量表说明';
}
}
function changeText(current: ScoreMetric, previous?: ScoreMetric) {
if (!previous) return '基线';
const diff = current.value - previous.value;
if (diff === 0) return '无变化';
const signed = `${diff > 0 ? '+' : ''}${diff}`;
switch (current.direction) {
case 'risk':
return diff > 0 ? `风险升高 ${signed}` : `风险降低 ${signed}`;
case 'protective':
case 'ability':
return diff > 0 ? `改善 ${signed}` : `下降 ${signed}`;
case 'trait':
return diff > 0 ? `特质增强 ${signed}` : `特质减弱 ${signed}`;
default:
return `变化 ${signed}`;
}
}
function MetricBar({ metric }: { metric: ScoreMetric }) {
const width = metric.max ? Math.max(0, Math.min(100, metric.value / metric.max * 100)) : 0;
return (
@@ -244,14 +279,15 @@ export function RecordsDashboard() {
{trendGroups.map((group) => (
<div key={group[0].questionnaireId}>
<div className="mb-3 flex flex-wrap items-baseline justify-between gap-2"><h2 className="text-lg font-semibold">{group[0].questionnaireTitle}</h2>{group[0].recommendedInterval && <span className="text-sm text-muted-foreground">{group[0].recommendedInterval}</span>}</div>
<p className="mb-3 text-sm text-muted-foreground">{group[0].scoreSummary.highScoreMeaning} · {directionText(group[0].scoreSummary.direction)}</p>
<div className="overflow-x-auto border">
<table className="w-full min-w-[540px] text-sm"><thead className="bg-muted/50"><tr><th className="p-3 text-left"></th><th className="p-3 text-left"></th><th className="p-3 text-left"></th></tr></thead>
<tbody>{group.map((record, index) => { const metrics = record.scoreSummary.primary ? [record.scoreSummary.primary] : record.scoreSummary.metrics; const previous = group[index - 1]?.scoreSummary.primary; return <tr key={record.id} className="border-t"><td className="p-3">{displayDate(record.completedAt)}</td><td className="p-3">{metrics.map((item) => <span key={item.key} className="mr-4">{item.label} {item.value}{item.max ? `/${item.max}` : ''}</span>)}</td><td className="p-3">{record.scoreSummary.primary && previous ? `${record.scoreSummary.primary.value - previous.value > 0 ? '+' : ''}${record.scoreSummary.primary.value - previous.value}` : '基线'}</td></tr>; })}</tbody>
<table className="w-full min-w-[640px] text-sm"><thead className="bg-muted/50"><tr><th className="p-3 text-left"></th><th className="p-3 text-left"></th><th className="p-3 text-left"></th><th className="p-3 text-left"></th></tr></thead>
<tbody>{group.map((record, index) => { const metrics = record.scoreSummary.primary ? [record.scoreSummary.primary] : record.scoreSummary.metrics; const primary = record.scoreSummary.primary; const previous = group[index - 1]?.scoreSummary.primary; return <tr key={record.id} className="border-t"><td className="p-3">{displayDate(record.completedAt)}</td><td className="p-3">{metrics.map((item) => <span key={item.key} className="mr-4">{item.label} {item.value}{item.max ? `/${item.max}` : ''}</span>)}</td><td className="p-3">{directionText(primary?.direction || record.scoreSummary.direction)}</td><td className="p-3">{primary ? changeText(primary, previous) : '看各维度'}</td></tr>; })}</tbody>
</table>
</div>
</div>
))}
<p className="text-sm text-muted-foreground"></p>
<p className="text-sm text-muted-foreground"></p>
</section>
)}