📝 docs(vscode): 提交调用树弹框原型

This commit is contained in:
cruldra committed 2026-10-11 00:40:51 -04:00
1 parent b463706d1f
commit 6e8be1447a
7 files changed
+9852

No files matched your search

+771
View File
@@ -0,0 +1,771 @@
// 调用树原型。颜色:代码阅读暗色种类色(codeReader.css)叠在 VS Code Dark Modern 的编辑器底上。
// 要确认的事:点函数或类卡片右上角,调用树用弹框盖在预览上,预览不换掉。通向当前块的路径亮着;多棵树在弹框里切换。
const UI = "Noto Sans SC";
const MONO = "Menlo";
const C = {
bg: "#1F1F1F",
side: "#181818",
fg: "#CCCCCC",
muted: "#9D9D9D",
dim: "#6E6E6E",
line: "#2B2B2B",
guide: "#3C3C3C",
tab: "#181818",
selected: "#04395E",
chip: "#313131",
chipLine: "#3C3C3C",
focus: "#007FD4",
badge: "#0078D4",
white: "#FFFFFF",
ink: "#1F1F1F",
};
// 函数种类 #DCDCAA、类种类 #4EC9B0,按阅读器卡片的 7% 底、35% 边,当前块加到 14% 底、实色边。
const FN = { bg: "#2C2C29", bd: "#616150", hot: "#3A3A32", ink: "#DCDCAA" };
const CLS = { bg: "#222B29", bd: "#305A52", hot: "#263733", ink: "#4EC9B0" };
const solid = (color) => [{ type: "solid", color }];
const TREES = [
{ label: "读取用户资料", count: 2 },
{ label: "导出通讯录", count: 1 },
{ label: "夜间对账", count: 1 },
];
const PHONE = {
name: "GET /users/me",
icon: "route",
desc: "入口",
loc: "routes/user.py",
on: true,
children: [
{
name: "get_profile",
icon: "braces",
desc: "读取当前用户",
loc: "services/user.py",
on: true,
children: [
{
name: "serialize_user",
icon: "braces",
desc: "写成接口响应",
loc: "services/user.py",
on: true,
children: [
{
name: "mask_phone",
icon: "braces",
desc: "手机号打星",
loc: "utils/phone.py:28",
on: true,
current: true,
},
{ folded: "另外 1 处调用" },
],
},
{
name: "load_user",
icon: "braces",
desc: "从库里取出用户",
loc: "services/user.py",
on: true,
children: [
{
name: "mask_phone",
icon: "braces",
desc: "手机号打星",
loc: "utils/phone.py:28",
on: true,
current: true,
},
],
},
{ folded: "另外 2 处调用" },
],
},
],
};
const ORDER = {
name: "POST /orders",
icon: "route",
desc: "入口",
loc: "routes/order.py",
on: true,
children: [
{
name: "create_order",
icon: "braces",
desc: "创建一笔订单",
loc: "billing/order.py",
on: true,
children: [
{
name: "新建 OrderService",
icon: "box",
desc: "进入这个类",
loc: "billing/order.py:40",
on: true,
children: [
{
name: "OrderService",
icon: "box",
desc: "订单结算",
loc: "billing/order.py:14",
on: true,
current: true,
children: [{ folded: "4 个方法不在这条路径上" }],
},
],
},
],
},
],
};
const SINGLE = {
name: "夜间对账",
icon: "route",
desc: "入口",
loc: "jobs/audit.py",
on: true,
children: [
{
name: "run_audit",
icon: "braces",
desc: "跑一遍对账",
loc: "jobs/audit.py",
on: true,
children: [
{
name: "mask_phone",
icon: "braces",
desc: "手机号打星",
loc: "utils/phone.py:28",
on: true,
current: true,
},
],
},
],
};
function text(parent, content, opts) {
return I(parent, Object.assign({
type: "text",
content,
fontFamily: UI,
fontSize: 13,
fontWeight: 400,
fill: solid(C.fg),
textGrowth: "auto",
lineHeight: 1.3,
}, opts));
}
function icon(parent, name, color) {
return I(parent, {
type: "icon_font",
iconFontName: name,
width: 14,
height: 14,
fill: solid(color),
});
}
function tool(parent, name, badge, on) {
const props = {
type: "frame",
layout: "horizontal",
width: "fit_content",
height: 20,
padding: [0, 4],
gap: 1,
alignItems: "center",
cornerRadius: 4,
};
if (on) props.fill = solid(C.selected);
const btn = I(parent, props);
icon(btn, name, on ? C.white : C.muted);
if (badge) {
const mark = I(btn, {
type: "frame",
layout: "horizontal",
width: 14,
height: 14,
cornerRadius: 7,
alignItems: "center",
justifyContent: "center",
fill: solid(C.badge),
});
text(mark, badge, { fontSize: 9, fontWeight: 700, fill: solid(C.white) });
}
return btn;
}
function nodeRow(parent, node, depth, tone) {
const row = I(parent, {
type: "frame",
name: node.folded || node.name,
layout: "horizontal",
width: "fill_container",
height: "fit_content",
padding: [0, 0, 0, depth * 22],
gap: 8,
alignItems: "center",
});
I(row, {
type: "rectangle",
width: 2,
height: node.folded ? 16 : 44,
cornerRadius: 1,
fill: solid(node.on ? tone.ink : C.guide),
});
if (node.folded) {
text(row, node.folded, { fontSize: 12, fill: solid(C.dim) });
return;
}
const card = I(row, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fit_content",
padding: [7, 10],
gap: 2,
cornerRadius: 8,
fill: solid(node.current ? tone.hot : tone.bg),
stroke: {
thickness: 1,
align: "inside",
fill: solid(node.current ? tone.ink : tone.bd),
},
});
const top = I(card, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: "fit_content",
gap: 8,
alignItems: "center",
});
icon(top, node.icon, tone.ink);
text(top, node.name, { fontFamily: MONO, fontSize: 13, fontWeight: 600, fill: solid(tone.ink) });
I(top, { type: "frame", width: "fill_container", height: 1 });
if (node.current) {
const pill = I(top, {
type: "frame",
layout: "horizontal",
height: 18,
padding: [0, 6],
cornerRadius: 9,
alignItems: "center",
fill: solid(tone.ink),
});
text(pill, "当前", { fontSize: 11, fontWeight: 600, fill: solid(C.ink) });
}
const sub = I(card, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: "fit_content",
gap: 8,
alignItems: "center",
});
text(sub, node.desc, { fontSize: 12, fill: solid(C.muted) });
text(sub, node.loc, { fontFamily: MONO, fontSize: 11, fill: solid(C.dim) });
}
function walk(parent, node, depth, tone) {
nodeRow(parent, node, depth, tone);
for (const child of node.children || []) walk(parent, child, depth + 1, tone);
}
function editor(parent, spec) {
const shell = I(parent, {
type: "frame",
x: spec.x,
y: spec.y,
layout: "horizontal",
width: spec.width,
height: spec.height,
fill: solid(C.bg),
clipContent: true,
});
if (spec.sideWidth) {
const side = I(shell, {
type: "frame",
layout: "vertical",
width: spec.sideWidth,
height: "fill_container",
fill: solid(C.side),
});
const sideHead = I(side, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 35,
padding: [0, 12],
alignItems: "center",
});
text(sideHead, "资源管理器", { fontSize: 11, fill: solid(C.muted) });
for (const file of spec.files) fileRow(side, file.label, file.depth, file.on);
}
const main = I(shell, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fill_container",
fill: solid(C.bg),
});
const tabs = I(main, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 35,
padding: [0, 0, 0, 8],
alignItems: "end",
fill: solid(C.tab),
});
const tab = I(tabs, {
type: "frame",
layout: "horizontal",
height: 35,
padding: [0, 12],
gap: 8,
alignItems: "center",
fill: solid(C.bg),
});
text(tab, spec.tab, { fontSize: 13 });
icon(tab, "x", C.dim);
I(main, { type: "rectangle", width: "fill_container", height: 1, fill: solid(C.line) });
const page = I(main, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fit_content",
padding: 16,
gap: 8,
});
for (const card of spec.cards) readerCard(page, card);
}
function dialogBody(dialog, spec) {
const bar = I(dialog, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 44,
padding: [0, 14],
gap: 8,
alignItems: "center",
fill: solid("#2D2D2D"),
});
text(bar, "调用树", { fontSize: 13, fontWeight: 600 });
text(bar, spec.symbol, { fontFamily: MONO, fontSize: 13, fontWeight: 600, fill: solid(spec.tone.ink) });
text(bar, spec.where, { fontFamily: MONO, fontSize: 11, fill: solid(C.dim) });
I(bar, { type: "frame", width: "fill_container", height: 1 });
icon(bar, "x", C.muted);
I(dialog, { type: "rectangle", width: "fill_container", height: 1, fill: solid(C.line) });
if (spec.chips) {
const chips = I(dialog, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: "fit_content",
padding: [12, 16, 4, 16],
gap: 8,
alignItems: "center",
});
for (const chip of spec.chips) {
const box = I(chips, {
type: "frame",
layout: "horizontal",
height: 28,
padding: [0, 10],
gap: 6,
alignItems: "center",
cornerRadius: 6,
fill: solid(chip.on ? C.selected : C.chip),
stroke: { thickness: 1, align: "inside", fill: solid(chip.on ? C.focus : C.chipLine) },
});
text(box, chip.label, { fontSize: 12, fontWeight: chip.on ? 600 : 400, fill: solid(chip.on ? C.white : C.fg) });
text(box, chip.count + " 处", { fontSize: 11, fill: solid(chip.on ? "#9CDCFF" : C.dim) });
}
}
const body = I(dialog, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fit_content",
padding: [12, 16, 20, 16],
gap: 6,
});
walk(body, spec.tree, 0, spec.tone);
}
function treeBoard(spec) {
const editorW = spec.width - spec.sideWidth;
const stageH = spec.height - 36;
const root = I(null, {
type: "frame",
name: spec.name,
x: spec.x,
y: 0,
layout: "horizontal",
width: spec.width,
height: spec.height,
fill: solid(C.bg),
clipContent: true,
});
if (spec.sideWidth) {
const side = I(root, {
type: "frame",
layout: "vertical",
width: spec.sideWidth,
height: "fill_container",
fill: solid(C.side),
});
const sideHead = I(side, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 35,
padding: [0, 12],
alignItems: "center",
});
text(sideHead, "资源管理器", { fontSize: 11, fill: solid(C.muted) });
for (const file of spec.files) fileRow(side, file.label, file.depth, file.on);
}
const main = I(root, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fill_container",
fill: solid(C.bg),
});
const tabs = I(main, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 35,
padding: [0, 0, 0, 8],
alignItems: "end",
fill: solid(C.tab),
});
const tab = I(tabs, {
type: "frame",
layout: "horizontal",
height: 35,
padding: [0, 12],
gap: 8,
alignItems: "center",
fill: solid(C.bg),
});
text(tab, spec.tab, { fontSize: 13 });
icon(tab, "x", C.dim);
I(main, { type: "rectangle", width: "fill_container", height: 1, fill: solid(C.line) });
const stage = I(main, {
type: "frame",
layout: "none",
width: "fill_container",
height: stageH,
fill: solid(C.bg),
});
const dialog = I(stage, {
type: "frame",
name: "弹框",
x: spec.dialogX,
y: spec.dialogY,
layout: "vertical",
width: spec.dialogW,
height: "fit_content",
cornerRadius: 8,
clipContent: true,
fill: solid("#252526"),
stroke: { thickness: 1, align: "inside", fill: solid("#454545") },
effects: [{ type: "shadow", offsetX: 0, offsetY: 16, blur: 40, color: "rgba(0,0,0,0.5)" }],
});
dialogBody(dialog, spec);
I(stage, {
type: "rectangle",
name: "遮罩",
x: 0,
y: 0,
width: editorW,
height: stageH,
fill: solid("#000000"),
opacity: 0.55,
});
const page = I(stage, {
type: "frame",
x: 16,
y: 16,
layout: "vertical",
width: editorW - 32,
height: "fit_content",
gap: 8,
});
for (const card of spec.cards) readerCard(page, card);
return root;
}
function readerCard(parent, spec) {
const card = I(parent, {
type: "frame",
name: spec.symbol,
layout: "horizontal",
width: "fill_container",
height: "fit_content",
padding: [8, 10, 8, 8],
gap: 8,
alignItems: "start",
cornerRadius: 8,
fill: solid(spec.tone.bg),
stroke: { thickness: 1, align: "inside", fill: solid(spec.tone.bd) },
});
icon(card, "chevron-down", C.muted);
icon(card, spec.icon, spec.tone.ink);
const body = I(card, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fit_content",
gap: 4,
});
text(body, spec.symbol, { fontFamily: MONO, fontSize: 13, fontWeight: 600, fill: solid(spec.tone.ink) });
text(body, spec.desc, { fontSize: 13, fill: solid(C.fg) });
text(body, spec.loc, { fontFamily: MONO, fontSize: 11, fill: solid(C.dim) });
const tools = I(card, {
type: "frame",
layout: "horizontal",
width: "fit_content",
height: 20,
gap: 2,
alignItems: "center",
});
tool(tools, "message-square-text");
tool(tools, "sparkles");
tool(tools, "pencil");
tool(tools, "git-branch", spec.badge, spec.open);
}
function fileRow(parent, label, depth, on) {
const row = I(parent, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 26,
padding: [0, 8, 0, 8 + depth * 14],
gap: 6,
alignItems: "center",
fill: solid(on ? C.selected : C.side),
});
text(row, label, { fontSize: 13, fill: solid(on ? C.white : C.fg) });
}
function readerBoard() {
const root = I(null, {
type: "frame",
name: "代码阅读 · 卡片入口",
x: 0,
y: 0,
layout: "horizontal",
width: 1040,
height: 220,
fill: solid(C.bg),
clipContent: true,
});
const side = I(root, {
type: "frame",
layout: "vertical",
width: 220,
height: "fill_container",
fill: solid(C.side),
});
const sideHead = I(side, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 35,
padding: [0, 12],
alignItems: "center",
});
text(sideHead, "资源管理器", { fontSize: 11, fill: solid(C.muted) });
fileRow(side, "superwork", 0, false);
fileRow(side, "routes", 1, false);
fileRow(side, "services", 1, false);
fileRow(side, "utils", 1, false);
fileRow(side, "phone.py", 2, true);
const main = I(root, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fit_content",
fill: solid(C.bg),
});
const tabs = I(main, {
type: "frame",
layout: "horizontal",
width: "fill_container",
height: 35,
padding: [0, 0, 0, 8],
alignItems: "end",
fill: solid(C.tab),
});
const tab = I(tabs, {
type: "frame",
layout: "horizontal",
height: 35,
padding: [0, 12],
gap: 8,
alignItems: "center",
fill: solid(C.bg),
});
text(tab, "phone.py", { fontSize: 13 });
icon(tab, "x", C.dim);
I(main, { type: "rectangle", width: "fill_container", height: 1, fill: solid(C.line) });
const page = I(main, {
type: "frame",
layout: "vertical",
width: "fill_container",
height: "fit_content",
padding: 16,
gap: 8,
});
readerCard(page, {
symbol: "mask_phone",
icon: "braces",
desc: "把手机号中间四位换成星号",
loc: "utils/phone.py:28",
tone: FN,
badge: "3",
});
readerCard(page, {
symbol: "mask_email",
icon: "braces",
desc: "把邮箱用户名打成星号",
loc: "utils/phone.py:41",
tone: FN,
badge: "1",
});
return root;
}
const PHONE_FILES = [
{ label: "superwork", depth: 0 },
{ label: "routes", depth: 1 },
{ label: "services", depth: 1 },
{ label: "utils", depth: 1 },
{ label: "phone.py", depth: 2, on: true },
];
const PHONE_CARDS = [
{
symbol: "mask_phone",
icon: "braces",
desc: "把手机号中间四位换成星号",
loc: "utils/phone.py:28",
tone: FN,
badge: "3",
open: true,
},
{
symbol: "mask_email",
icon: "braces",
desc: "把邮箱用户名打成星号",
loc: "utils/phone.py:41",
tone: FN,
badge: "1",
},
];
readerBoard();
treeBoard({
name: "调用树 · 弹框,多棵",
x: 1120,
width: 1040,
height: 720,
sideWidth: 220,
dialogX: 28,
dialogY: 28,
dialogW: 764,
tab: "phone.py",
files: PHONE_FILES,
cards: PHONE_CARDS,
symbol: "mask_phone",
where: "utils/phone.py:28",
tone: FN,
chips: TREES.map((tree, index) => Object.assign({ on: index === 0 }, tree)),
tree: PHONE,
});
treeBoard({
name: "调用树 · 弹框,类",
x: 2240,
width: 1040,
height: 640,
sideWidth: 220,
dialogX: 28,
dialogY: 28,
dialogW: 764,
tab: "order.py",
files: [
{ label: "superwork", depth: 0 },
{ label: "billing", depth: 1 },
{ label: "order.py", depth: 2, on: true },
],
cards: [{
symbol: "OrderService",
icon: "box",
desc: "订单结算",
loc: "billing/order.py:14",
tone: CLS,
badge: "2",
open: true,
}],
symbol: "OrderService",
where: "billing/order.py:14",
tone: CLS,
chips: [
{ label: "创建订单", count: 1, on: true },
{ label: "补开发票", count: 1, on: false },
],
tree: ORDER,
});
treeBoard({
name: "调用树 · 弹框,只有一棵",
x: 3360,
width: 1040,
height: 560,
sideWidth: 220,
dialogX: 28,
dialogY: 28,
dialogW: 764,
tab: "phone.py",
files: PHONE_FILES,
cards: PHONE_CARDS,
symbol: "mask_phone",
where: "utils/phone.py:28",
tone: FN,
tree: SINGLE,
});
treeBoard({
name: "调用树 · 弹框,窄窗口",
x: 4480,
width: 640,
height: 720,
sideWidth: 0,
dialogX: 20,
dialogY: 20,
dialogW: 600,
tab: "phone.py",
files: [],
cards: PHONE_CARDS,
symbol: "mask_phone",
where: "utils/phone.py:28",
tone: FN,
chips: TREES.map((tree, index) => Object.assign({ on: index === 0 }, tree)),
tree: PHONE,
});
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB