评级计算器并入客户管理后,列表只有分数看不出档位,本批补上等级徽标: - 抽出 utils/rating.ts 的 resolveGrade(按 sort_order 升序取首个 min_score ≤ 总分的等级,单边判定避免区间边界歧义),列表页与弹窗共用 - 等级映射按需拉取并缓存在内存,等级配置改动后回调父页重拉,避免 「同一分数在弹窗是 B、在列表是 C」的两处口径分歧 - 徽标色调按等级名首字母映射,自定义等级名落回中性灰(等级名可配置, 不能靠写死枚举判语义色) - 悬浮提示给出等级区间与评级时间 同时订正三处与实现不符的注释:rate_customer 实际只写 rating_score 与 rating_time,等级由分数按当前映射实时推导,三处却写成「后端判定并固化等级 名快照」,v13 迁移 docstring 还引用了并不存在的 v14(d5b8f2a46c71)。 纯注释改动,无逻辑变更。 Co-Authored-By: Claude Code <noreply@anthropic.com>
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import type { RatingGradeItem } from '@/api/ratings'
|
||
|
||
/**
|
||
* 评级等级判定(纯函数,客户列表与评级弹窗共用同一口径)。
|
||
*
|
||
* 规则:按 sort_order 升序取首个「min_score ≤ 总分」的等级,max_score 仅作
|
||
* 展示 —— 单边判定避免区间边界(如 75 分)在「≥75 且 ≤89」与「>74 且 <75」
|
||
* 两套写法间的重叠/遗漏歧义。
|
||
*
|
||
* 后端 min_score 是字符串直出(分值红线:分值全程不进 float),比较前必须
|
||
* parseFloat 转数值,禁止字符串直接比大小("90.0" 的字典序比较会得出错误
|
||
* 结果)。无命中(总分低于全部下限)返回 null,由调用方展示未命中提示。
|
||
*/
|
||
export function resolveGrade(
|
||
grades: RatingGradeItem[],
|
||
totalScore: number,
|
||
): RatingGradeItem | null {
|
||
const ordered = [...grades].sort((a, b) => a.sort_order - b.sort_order)
|
||
for (const grade of ordered) {
|
||
if (totalScore >= parseFloat(grade.min_score)) {
|
||
return grade
|
||
}
|
||
}
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 等级名称 → 色调后缀(客户列表的等级徽标用)。
|
||
*
|
||
* 按名称首字母映射到体系语义色:A 优秀(绿)、B 良好(靛蓝)、C 合格
|
||
* (琥珀)、D 待改进(砖红);自定义等级名不在此列,落回中性灰 —— 等级
|
||
* 名称是业务可配置的,不能靠写死的枚举判定语义,未知即中性最安全。
|
||
*/
|
||
export function gradeTone(gradeName: string): '' | 'ok' | 'lock' | 'wait' | 'reject' {
|
||
const initial = gradeName.trim().charAt(0).toUpperCase()
|
||
switch (initial) {
|
||
case 'A':
|
||
return 'ok'
|
||
case 'B':
|
||
return 'lock'
|
||
case 'C':
|
||
return 'wait'
|
||
case 'D':
|
||
return 'reject'
|
||
default:
|
||
return ''
|
||
}
|
||
}
|