图谱四视图切换(全景/技术栈聚类/职级演进/动态时序)+ 标签按需显示 + NEW 脉动标记(CSS/Canvas 实现,无 emoji)+ 时点对比 Git Style Diff 与证据关键词高亮 + 简历加分项改琥珀色 + 学习路径甘特图

This commit is contained in:
RoyirinRosmontis
2026-08-20 12:10:35 +08:00
parent b9fcfcb82e
commit c58e2e996f
5 changed files with 442 additions and 38 deletions
+43
View File
@@ -93,5 +93,48 @@ eq(weak.distinctness, 0, "技能完全重合时差异度为 0");
eq(stageEffortText(2), "2 周 / 约 16 小时", "阶段投入文案");
eq(stageEffortText(1), "1 周 / 约 8 小时", "单周投入文案");
/* ganttBars:甘特图横条位置(左偏移/宽度百分比) */
const ganttBars = loadFn("ganttBars");
const gb = ganttBars([2, 2, 4]);
eq(gb.length, 3, "甘特条数");
eq(gb[0].left + "," + gb[0].width, "0,25", "甘特首条位置");
eq(gb[2].left + "," + gb[2].width, "50,50", "甘特末条位置");
eq(ganttBars([4, 4])[1].left, 50, "等长两阶段第二条起点");
/* diffSkillRows:时点画像 Git 风格差集 */
const diffSkillRows = loadFn("diffSkillRows");
const d = diffSkillRows(
[{ skill: "Java", importance: "required" }, { skill: "JSP", importance: "preferred" }],
[{ skill: "Java", importance: "preferred" }, { skill: "Kubernetes", importance: "required" }]);
eq(d.add.map((r) => r.skill).join(","), "Kubernetes", "diff 新增");
eq(d.del.map((r) => r.skill).join(","), "JSP", "diff 退出");
eq(d.keep.length, 1, "diff 保留数");
eq(d.keep[0].up, true, "加分升级必备标记");
/* graph.js 纯逻辑:时序边生效判定 / 职级带归属(函数在 IIFE 内,按两空格缩进闭合提取) */
const gsrc = fs.readFileSync(path.join(__dirname, "..", "webui", "graph.js"), "utf-8");
function loadGraphFns(names) {
const code = names.map((n) => {
const m = gsrc.match(new RegExp(`function ${n}\\([\\s\\S]*?\\n \\}`));
if (!m) throw new Error(`graph.js 未找到函数 ${n}`);
return m[0];
}).join("\n");
return new Function(`${code}; return { ${names.join(", ")} };`)();
}
const gf = loadGraphFns(["jobLevel", "levelBandOf", "edgeActiveAt"]);
eq(gf.edgeActiveAt({ intervals: [] }, "2026Q1"), true, "无区间边始终生效");
eq(gf.edgeActiveAt({ intervals: [["2025Q1", "2025Q3"]] }, "2025Q2"), true, "区间内生效");
eq(gf.edgeActiveAt({ intervals: [["2025Q1", "2025Q3"]] }, "2025Q3"), false, "结束边界半开不生效");
eq(gf.edgeActiveAt({ intervals: [["2025Q1", "2025Q3"]] }, "2024Q4"), false, "起点前不生效");
eq(gf.edgeActiveAt({ intervals: [["2025Q1", null]] }, "2026Q4"), true, "开放区间持续生效");
eq(gf.jobLevel({ expected_years: 1 }), "初级", "≤2年初级");
eq(gf.jobLevel({ expected_years: 4 }), "中级", "3-5年中级");
eq(gf.jobLevel({ expected_years: 8 }), "高级", ">5年高级");
eq(gf.levelBandOf({ type: "Job", expected_years: 1 }), 0, "初级岗位入带0");
eq(gf.levelBandOf({ type: "Job", expected_years: 8 }), 2, "高级岗位入带2");
eq(gf.levelBandOf({ type: "Skill", level: "L1" }), 0, "L1技能入带0");
eq(gf.levelBandOf({ type: "Skill", level: "L3" }), 2, "L3技能入带2");
eq(gf.levelBandOf({ type: "Skill" }), 1, "无级别技能默认中级带");
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);