From beccaa4b6bb9b0ae3e847631feb16fc95ad56f46 Mon Sep 17 00:00:00 2001 From: RoyirinRosmontis Date: Thu, 20 Aug 2026 12:25:13 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=8C=E7=BA=A7=E8=A7=86=E5=9B=BE=E5=86=8D?= =?UTF-8?q?=E4=BF=AE=EF=BC=9A=E6=8A=80=E8=83=BD=E8=8A=82=E7=82=B9=E8=A1=A5?= =?UTF-8?q?=E8=AF=8D=E8=A1=A8=20level=20=E5=88=86=E5=B8=A6=EF=BC=88?= =?UTF-8?q?=E6=AD=A4=E5=89=8D=E5=85=A8=E9=83=A8=E8=90=BD=E4=B8=AD=E7=BA=A7?= =?UTF-8?q?=E5=B8=A6=EF=BC=89=EF=BC=8C=E5=B8=A6=E9=97=B4=E8=B7=9D=E6=8B=89?= =?UTF-8?q?=E5=A4=A7=E3=80=81=E6=8A=80=E8=83=BD/=E5=B2=97=E4=BD=8D?= =?UTF-8?q?=E5=88=86=E8=A1=8C=E9=99=90=E6=8E=92=EF=BC=8C=E5=AE=BD=E9=AB=98?= =?UTF-8?q?=E6=AF=94=205.2=E2=86=922.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/routes.py | 9 ++++++++- webui/graph.js | 16 ++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/api/routes.py b/app/api/routes.py index f85720e..827c3df 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -327,9 +327,16 @@ def restart_service() -> dict: @app.get("/api/graph") def graph() -> dict: - """返回节点与边(含时间区间)与演化事件,供前端渲染全景图谱。""" + """返回节点与边(含时间区间)与演化事件,供前端渲染全景图谱。 + + 技能节点附带词表 level(L1/L2/L3),前端职级演进视图按此分带。 + """ kg = _get_kg() nodes = [{"id": n, **d} for n, d in kg.g.nodes(data=True)] + skill_lv = {s["name"]: s.get("level") for s in load_lexicon(resolve(_cfg, "lexicon_path"))["skills"]} + for n in nodes: + if n.get("type") == "Skill" and not n.get("level"): + n["level"] = skill_lv.get(n["id"], "L2") edges = [{"source": u, "target": v, **d} for u, v, d in kg.g.edges(data=True)] events = sorted((e.model_dump() for e in kg.events), key=lambda e: e["window"]) return _ok({"nodes": nodes, "edges": edges, "events": events, "stats": kg.stats()}) diff --git a/webui/graph.js b/webui/graph.js index ef80fff..08be7cb 100644 --- a/webui/graph.js +++ b/webui/graph.js @@ -99,9 +99,9 @@ /* ================= 视图切换:全景 / 技术栈 / 职级演进 / 动态时序 ================= */ const VIEW_NAMES = { overview: "全景总览", tech: "技术栈视图", level: "职级演进视图", timeline: "动态时序视图" }; const LEVEL_BANDS = [ - { name: "初级 L1 · ≤2 年", y: -340 }, + { name: "初级 L1 · ≤2 年", y: -420 }, { name: "中级 L2 · 3-5 年", y: 0 }, - { name: "高级 L3 · >5 年", y: 340 }, + { name: "高级 L3 · >5 年", y: 420 }, ]; /* 技术栈视图:类别锚点网格分布,岗位聚在锚点旁,技能/场景/行业靠向所属岗位质心 */ @@ -153,19 +153,19 @@ const yBand = LEVEL_BANDS[b].y; const jl = G.nodes.filter((n) => n.type === "Job" && levelBandOf(n) === b) .sort((x, y) => String(x.title || x.id).localeCompare(String(y.title || y.id), "zh")); - // 岗位带内多行排列(一行排开会横向过长,fitView 按比例缩成横带) - const jPerRow = Math.max(3, Math.ceil(Math.sqrt(jl.length) * 1.8)); + // 岗位在带线下方(最多 2 行),技能在带线上方(最多 3 行),两者不重叠 + const jPerRow = Math.max(3, Math.ceil(jl.length / 2)); jl.forEach((n, i) => { n.x = ((i % jPerRow) - (jPerRow - 1) / 2) * 190; - n.y = yBand + 150 - Math.floor(i / jPerRow) * 85; + n.y = yBand + 90 + Math.floor(i / jPerRow) * 85; n.vx = 0; n.vy = 0; }); const sl = G.nodes.filter((n) => n.type !== "Job" && levelBandOf(n) === b) .sort((x, y) => String(x.title || x.id).localeCompare(String(y.title || y.id), "zh")); - const perRow = Math.max(4, Math.ceil(Math.sqrt(sl.length) * 1.9)); + const perRow = Math.max(4, Math.ceil(sl.length / 4)); sl.forEach((n, i) => { - n.x = ((i % perRow) - (perRow - 1) / 2) * 108; - n.y = yBand - 120 + Math.floor(i / perRow) * 72; + n.x = ((i % perRow) - (perRow - 1) / 2) * 100; + n.y = yBand - 170 + Math.floor(i / perRow) * 62; n.vx = 0; n.vy = 0; }); });