281 lines
6.5 KiB
TypeScript
281 lines
6.5 KiB
TypeScript
/**
|
|
* 健康状态Dashboard工具函数
|
|
*/
|
|
|
|
import {
|
|
HealthLevel,
|
|
HealthLevelConfig,
|
|
HEALTH_LEVEL_CONFIG,
|
|
PlatformScoreWithCompetitor,
|
|
ActionSuggestion,
|
|
} from "@/types/dashboard-health";
|
|
|
|
/**
|
|
* 根据评分获取健康等级
|
|
*/
|
|
export function getHealthLevel(score: number): HealthLevel {
|
|
if (score >= 80) return "excellent";
|
|
if (score >= 60) return "good";
|
|
if (score >= 40) return "pass";
|
|
return "danger";
|
|
}
|
|
|
|
/**
|
|
* 获取健康等级配置
|
|
*/
|
|
export function getHealthLevelConfig(level: HealthLevel): HealthLevelConfig {
|
|
return HEALTH_LEVEL_CONFIG[level];
|
|
}
|
|
|
|
/**
|
|
* 获取健康等级的CSS类名
|
|
*/
|
|
export function getHealthLevelClassName(level: HealthLevel): {
|
|
bg: string;
|
|
text: string;
|
|
border: string;
|
|
badge: string;
|
|
} {
|
|
const config = HEALTH_LEVEL_CONFIG[level];
|
|
return {
|
|
bg: config.color.bg,
|
|
text: config.color.text,
|
|
border: config.color.border,
|
|
badge: `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${config.color.bg} ${config.color.text}`,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 计算平台评分与竞品的差距
|
|
*/
|
|
export function calculateCompetitorGap(
|
|
platformScore: number,
|
|
competitorScore?: number,
|
|
): {
|
|
ahead: boolean;
|
|
behind: boolean;
|
|
equal: boolean;
|
|
gap: number;
|
|
text: string;
|
|
} {
|
|
if (competitorScore === undefined || competitorScore === null) {
|
|
return {
|
|
ahead: false,
|
|
behind: false,
|
|
equal: true,
|
|
gap: 0,
|
|
text: "暂无竞品数据",
|
|
};
|
|
}
|
|
|
|
const gap = platformScore - competitorScore;
|
|
|
|
if (gap > 0) {
|
|
return {
|
|
ahead: true,
|
|
behind: false,
|
|
equal: false,
|
|
gap,
|
|
text: `领先${gap}分`,
|
|
};
|
|
} else if (gap < 0) {
|
|
return {
|
|
ahead: false,
|
|
behind: true,
|
|
equal: false,
|
|
gap: Math.abs(gap),
|
|
text: `落后${Math.abs(gap)}分`,
|
|
};
|
|
} else {
|
|
return {
|
|
ahead: false,
|
|
behind: false,
|
|
equal: true,
|
|
gap: 0,
|
|
text: "持平",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取趋势图标和颜色
|
|
*/
|
|
export function getTrendStyle(change: number): {
|
|
icon: "up" | "down" | "neutral";
|
|
color: string;
|
|
bgColor: string;
|
|
text: string;
|
|
} {
|
|
if (change > 0) {
|
|
return {
|
|
icon: "up",
|
|
color: "text-emerald-600",
|
|
bgColor: "bg-emerald-50",
|
|
text: `+${change.toFixed(1)}%`,
|
|
};
|
|
} else if (change < 0) {
|
|
return {
|
|
icon: "down",
|
|
color: "text-red-600",
|
|
bgColor: "bg-red-50",
|
|
text: `${change.toFixed(1)}%`,
|
|
};
|
|
} else {
|
|
return {
|
|
icon: "neutral",
|
|
color: "text-gray-500",
|
|
bgColor: "bg-gray-50",
|
|
text: "0%",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取竞品地位信息
|
|
*/
|
|
export function getCompetitorStatus(
|
|
ahead: number,
|
|
behind: number,
|
|
): {
|
|
label: string;
|
|
color: string;
|
|
status: "leading" | "trailing" | "mixed";
|
|
} {
|
|
if (ahead > 0 && behind === 0) {
|
|
return {
|
|
label: `领先${ahead}竞品`,
|
|
color: "text-emerald-600",
|
|
status: "leading",
|
|
};
|
|
} else if (behind > 0 && ahead === 0) {
|
|
return {
|
|
label: `落后${behind}竞品`,
|
|
color: "text-red-600",
|
|
status: "trailing",
|
|
};
|
|
} else if (ahead > 0 && behind > 0) {
|
|
return {
|
|
label: `领先${ahead} / 落后${behind}`,
|
|
color: "text-yellow-600",
|
|
status: "mixed",
|
|
};
|
|
} else {
|
|
return {
|
|
label: "暂无竞品数据",
|
|
color: "text-gray-500",
|
|
status: "mixed",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成行动建议
|
|
*/
|
|
export function generateActionSuggestions(stats: {
|
|
platformScores: PlatformScoreWithCompetitor[];
|
|
overallScore: number;
|
|
hasQueries: boolean;
|
|
}): ActionSuggestion[] {
|
|
const suggestions: ActionSuggestion[] = [];
|
|
|
|
// 找出落后最多的平台
|
|
const behindPlatforms = stats.platformScores
|
|
.filter((p) => {
|
|
const compScore = p.competitor_score ?? p.competitorScore;
|
|
const gap = calculateCompetitorGap(p.score, compScore);
|
|
return gap.behind;
|
|
})
|
|
.sort((a, b) => {
|
|
const gapA = calculateCompetitorGap(
|
|
a.score,
|
|
a.competitor_score ?? a.competitorScore,
|
|
);
|
|
const gapB = calculateCompetitorGap(
|
|
b.score,
|
|
b.competitor_score ?? b.competitorScore,
|
|
);
|
|
return (gapB.gap || 0) - (gapA.gap || 0);
|
|
});
|
|
|
|
if (behindPlatforms.length > 0) {
|
|
const worst = behindPlatforms[0];
|
|
const compScore = worst.competitor_score ?? worst.competitorScore;
|
|
const gap = calculateCompetitorGap(worst.score, compScore);
|
|
const platformName = worst.platform;
|
|
suggestions.push({
|
|
id: "competitor-gap",
|
|
type: "primary",
|
|
title: `您的品牌在${platformName}平台落后竞品${gap.gap}分`,
|
|
description: "点击查看差距分析,制定提升策略",
|
|
icon: "⚠️",
|
|
href: `/compare?platform=${worst.platform}`,
|
|
priority: 1,
|
|
});
|
|
}
|
|
|
|
// 检查是否有危险等级的平台
|
|
const dangerPlatforms = stats.platformScores.filter(
|
|
(p) => getHealthLevel(p.score) === "danger",
|
|
);
|
|
if (dangerPlatforms.length > 0) {
|
|
const platformName = dangerPlatforms[0].platform;
|
|
suggestions.push({
|
|
id: "danger-platform",
|
|
type: "primary",
|
|
title: `${platformName}平台评分危险(${dangerPlatforms[0].score}分)`,
|
|
description: "立即查看原因并采取行动",
|
|
icon: "🚨",
|
|
href: `/dashboard/queries?platform=${dangerPlatforms[0].platform}`,
|
|
priority: 0,
|
|
});
|
|
}
|
|
|
|
// 如果没有查询词
|
|
if (!stats.hasQueries) {
|
|
suggestions.push({
|
|
id: "add-query",
|
|
type: "secondary",
|
|
title: "添加查询词以获得更完整的分析",
|
|
description: "创建第一个查询词,系统将自动开始收集引用数据",
|
|
icon: "💡",
|
|
href: "/dashboard/queries",
|
|
priority: 10,
|
|
});
|
|
}
|
|
|
|
// 如果综合评分低于良好
|
|
if (stats.overallScore < 60) {
|
|
suggestions.push({
|
|
id: "improve-score",
|
|
type: "secondary",
|
|
title: "品牌综合评分需要提升",
|
|
description: "查看高分品牌的优化策略",
|
|
icon: "📈",
|
|
href: "/compare",
|
|
priority: 11,
|
|
});
|
|
}
|
|
|
|
// 可选行动
|
|
suggestions.push({
|
|
id: "set-alert",
|
|
type: "optional",
|
|
title: "设置竞品超越预警",
|
|
description: "当竞品评分超过您时,第一时间收到通知",
|
|
icon: "🔔",
|
|
href: "/dashboard/settings",
|
|
priority: 20,
|
|
});
|
|
|
|
return suggestions.sort((a, b) => a.priority - b.priority);
|
|
}
|
|
|
|
/**
|
|
* 获取危险平台列表
|
|
*/
|
|
export function getDangerPlatforms(
|
|
platformScores: PlatformScoreWithCompetitor[],
|
|
): PlatformScoreWithCompetitor[] {
|
|
return platformScores.filter((p) => getHealthLevel(p.score) === "danger");
|
|
}
|