diff --git a/poc/business-plugins/lib/client.js b/poc/business-plugins/lib/client.js index b2d1498..4b78f68 100644 --- a/poc/business-plugins/lib/client.js +++ b/poc/business-plugins/lib/client.js @@ -1204,17 +1204,29 @@ window.__ModuleLoader__.load({ return jsxRuntime.jsx("div", { style: wrap, children: rows }); } - ctx.slots.inject("settings.section", function () { - return ctx.slots.register( - { - name: "settings.section", - id: "platform-admin", - order: 102, - label: function () { return t("pa.label"); } - }, - PlatformAdminSection - ); - }); + // ── 「系统管理」分区:**仅 admin 注册**(2026-09-13 用户指出:普通用户不该看到这一栏) + // 依据:官方 settings 外壳按 `ctx.slots.getVersion("settings.section")` 失效重算导航 + // (`dsh-client-ui-settings/lib/client.js:560`)⇒ **晚注册会被正确反映**。 + // 所以先异步判角色:**非 admin 干脆不注册**(而非注册后显示一行「仅管理员」)。 + fetch(portalHost() + "/api/auth/me", { credentials: "include" }) + .then(function (r) { return r.ok ? r.json() : null; }) + .then(function (d) { + var role = d && d.user && d.user.role; + if (role !== "admin") return; + ctx.slots.inject("settings.section", function () { + return ctx.slots.register( + { + name: "settings.section", + id: "platform-admin", + order: 102, + label: function () { return t("pa.label"); } + }, + PlatformAdminSection + ); + }); + }) + // 判角色失败 = 按非 admin 处理,不注册(fail-closed:宁可少显示,不越权暴露) + .catch(function () {}); } exports.apply = apply; diff --git a/poc/business-plugins/package.json b/poc/business-plugins/package.json index 86a8dc8..018ae73 100644 --- a/poc/business-plugins/package.json +++ b/poc/business-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@dsh-local/business-plugins", - "version": "0.3.1", + "version": "0.3.2", "description": "功能管理(原「功能插件」)section for dsh web profile — v0.2.8(2026-09-13):**所有弹窗改页内弹窗** —— 弃用 window.confirm,改为 fixed 遮罩 + 居中面板 + 点遮罩/✕ 关闭(视觉与交互对齐 MCN 工作台 dsh-plugin-mcn 的 modalTitle/modalText/modalBtns 三段结构);**超限时弹窗内红底警告块**(标题 + 说明 + 内存数字),确认按钮转危险色,未点确认不发请求。v0.2.7(档案 75 维度 B 落地 · 2026-09-13):卡片新增「预估内存 ≈ N MiB」徽章(按实测成本分色:≥60 红 / ≥30 黄)、卡片加高(minHeight 112 + padding 14/16)便于四层信息排布;**列表上方新增「内存预估」状态条** —— 实心段=当前已启用插件预估占用、半透明段=本次勾选增量、超单实例上限(384 MiB)整条转红并给文字警告,确认弹窗也带上内存预估。口径 = 空载基线 285 MiB + 逐插件实测加载成本(隔离 cgroup 的 rss 增量),**是预估值不是实时读数**。候选池列表 + 搜索/状态徽章 + 批量启用/禁用 + 确认弹窗 + 重启后自动刷新。v0.2.4(档案 67):对齐 06-工作台UI规范 —— **信息层次反转**(主视觉改为插件用途说明,插件名/版本退为副行小字;中文说明由候选池入库时优先取官方目录)、字号阶梯 14/13/12、徽章与按钮改规范量级、空态改「标题 + 说明」、新增列表行 hover 反馈。v0.2.2:分区名由「功能插件」改为「功能管理」。v0.2.0:配色改用官方 --dsw-* design token(跟随 dsh 主题);文案走官方 locale(zh/en)。", "type": "module", "main": "lib/index.js", diff --git a/scripts/verify-platform-admin-section.mjs b/scripts/verify-platform-admin-section.mjs index 2657e02..af9d859 100644 --- a/scripts/verify-platform-admin-section.mjs +++ b/scripts/verify-platform-admin-section.mjs @@ -67,6 +67,8 @@ function dICT_zh(d) { return d.zh || {}; } const inj = Array.isArray(mod.inject) ? mod.inject : []; console.log(" inject 声明:", JSON.stringify(inj)); mod.apply(ctx); +// ⚠️ apply 里「仅 admin 注册」是**异步**的(先 fetch /api/auth/me 判角色)⇒ 必须等一拍 +await new Promise((r) => setTimeout(r, 80)); console.log(" 已注册 section:", regs.map(r => r.meta.id + " / order=" + r.meta.order + " / label=" + r.meta.label()).join(" | ")); function text(n, out = [], depth = 0) { @@ -98,7 +100,9 @@ for (const { meta, Comp } of regs.filter(r => r.meta.id === "platform-admin")) { } } -ok("注册了两个 settings.section", regs.length === 2, "-> " + regs.map(r => r.meta.id).join(", ")); +// 注:apply 里「仅 admin 注册」是异步判角色后才 inject;本脚本打桩的是同步路径, +// 因此这里断言「两个分区都可注册」+ 内容层门禁。注册条件由 R2 的运行时口径另验。 +ok("admin 视角下注册了两个 settings.section", regs.length === 2, "-> " + regs.map(r => r.meta.id).join(", ")); const pa = regs.find(r => r.meta.id === "platform-admin"); ok("存在 platform-admin 分区", !!pa); if (pa) ok("其 order = 102", pa.meta.order === 102); @@ -111,5 +115,15 @@ ROLE = "active"; const tUser = text(await render(pa.Comp)).join(" | "); ok("非 admin:被门禁挡住", tUser.includes("仅对管理员显示")); ok("非 admin:不出现任何管理项", !["候选池","运行时"].some(k => tUser.includes(k))); +// ── 关键:**非 admin 时「系统管理」压根不注册**(2026-09-13 用户指出普通用户不该看到这一栏) +{ + regs = []; ROLE = "active"; + mod.apply(ctx); + await new Promise((r) => setTimeout(r, 80)); + ok("非 admin 视角下只注册 1 个分区(不含系统管理)", + regs.length === 1 && regs[0].meta.id === "business-plugins", + "-> " + regs.map(r => r.meta.id).join(", ")); + regs = []; ROLE = "admin"; +} console.log(failed === 0 ? "\n结论:全绿 ✅" : "\n结论:有 " + failed + " 项失败 ❌"); process.exit(failed === 0 ? 0 : 1);