# 100 · 实例内「我的技能」—— 并入「功能管理」的分组方案与实现 - 日期:2026-09-15 - 触发:用户「规划一个产品方案(界面布局美观 方便操作)然后实现这个功能」=落地 `交接单/T01`(档案 16 阶段 3/4) - 对象:`poc/business-plugins`(`@dsh-local/business-plugins`)client bundle 的「功能管理」section - 状态:✅ **已实施并投放**(`business-plugins` **0.3.21**,两实例;方案见 §二,验收见 §8.2/§8.4) - ⚠️ 后续:该分区已改名「**能力管理**」并改为**页内 tab 分页**(档案 **101**);本档案正文保留当时的形态描述(档案只增不改) > **TL;DR**|**结论**:在**既有「功能管理」section 内新增「我的技能」分组**(**不新开 section**),用户在同一页看完并管理「功能插件 + 我的技能」两类功能;技能行支持 上传 / 启用 / 停用 / 删除,平台共享技能显示为**锁定只读**。 > **关键**:入口层合并、**机制层分离**(技能 watch 即时生效 vs 插件需重启;API / 落盘各自独立)—— 依据见 `交接单/T01 §九`。 > **不做**:不改服务端(6 个 `/api/skills/mine*` 路由早已就绪,档案 41);不新造包;不碰官方包。 --- ## 一、目标与完成判定 **目标**:普通用户在实例内**自助管理个人技能**,且平台投放/用户自传两类"功能"在**同一页、同一套交互语汇**下呈现。 **完成判定(可被第三方复现)**: 1. guest 实例 → 设置 →「功能管理」页内出现「**我的技能**」分组,且与「功能插件」同页可见; 2. 上传一个合法 `.zip` 技能 → 行内新增且状态「已启用」;`$DSH_HOME/skills/` 存在; 3. 停用 → 文件移到 `home/skills-library/`;启用 → 回到 `skills/`;删除 → 两处均无; 4. 与共享技能同名上传 / 对锁定行操作 → 后端 409,界面以**行内错误**如实呈现; 5. 全程**无需重启实例**(技能是 watch 驱动)。 --- ## 二、产品方案(界面布局) ### 2.1 页内结构(自上而下,同页滚动) ``` ┌─ 功能管理 ───────────────────────────────────────────────────────┐ │ │ │ ▍功能插件 ← 既有分组(不动) │ │ ├─ 内存配额状态条(既有) │ │ ├─ 工具行:搜索 / 全选 / 清空 / 已选 N 项(既有) │ │ └─ 插件卡片网格(既有,勾选式) │ │ │ │ ─────────────── 1px 分隔线 ─────────────── │ │ │ │ ▍我的技能 [3] [+ 上传技能] ← 新增分组(本单) │ │ ├─ 说明行:上传 .zip 技能包;启用/停用**立即生效,无需重启** │ │ ├─ 上传面板(默认收起,点「上传技能」展开 / 支持拖入) │ │ └─ 技能行列表 │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 账号诊断 [共享]🔒只读 12 文件 · 136 KB │ │ │ │ 我的图表工具 [我的]●已启用 8 文件 · 24 KB [停用][删除]│ │ │ 旧版脚本 [我的]○已停用 5 文件 · 12 KB [启用][删除]│ │ └──────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` **为什么是这个结构**:用户在「功能管理」里的心智是"**我有哪些功能、开没开**"。两类功能放同一页、同一行卡片语汇,一次滚动看完;差异(要不要重启)由**行内事实文案**承担,而不是靠分成两页去解释。 ### 2.2 分组头(`.bp-group-h`) | 元素 | 规格 | |---|---| | 标题 | 15px / 600 / `T.text`,前缀 3px 竖条(`T.primary`,圆角 2px)—— 与「功能插件」分组头**同一形态** | | 计数徽章 | 13px,`T.field` 底 + `T.sub` 字,`2px 8px` r10(规范 §4.6 `.badge` 量级) | | 右侧主按钮 | 「+ 上传技能」,白底 + `T.border` 边 + `T.primary` 字(规范 §4.1 `.btn-view` 量级),点击展开/收起上传面板(`aria-expanded`) | | 与上方分组的分隔 | `1px solid T.border` + `margin-top: 22px; padding-top: 18px`(规范 §2.4 间距序列) | ### 2.3 技能行(`.bp-skill-row`)—— 单行不换行 | 区 | 内容 | 规格 | |---|---|---| | 左 | **技能名**(`name`) | 14px / 500 / `nowrap` + ellipsis + `title`(规范 §5 长文本截断三件套);`min-width:0` 允许收缩 | | 中 | **来源徽章** | `共享` = `T.primary` 12% 底 + `T.primary` 字;`我的` = `T.field` 底 + `T.sub` 字。13px / r10 | | 中 | **状态** | ● 点(6px 圆点)+ 文字:已启用 = `T.success`;已停用 = `T.dim`。13px | | 中 | **事实**(右对齐) | `12 文件 · 136 KB`,13px + `font-variant-numeric: tabular-nums`(规范 §5「数值一律右对齐 + 等宽数字」) | | 右 | **动作区** | `margin-left:auto` + `nowrap`;启用/停用 = 白底 border 按钮(`.btn-sm` 量级);删除 = `T.danger` 文字按钮。**busy 时该行按钮统一 disabled + opacity .5** | | 整行 | 容器 | `padding: 12px 14px`,`border: 1px solid T.border`,r12,白底(`--dsw-alias-bg-layer-1`),hover 变 `T.field` 底 | **锁定行(`locked: true`,平台共享技能)**:来源徽章后追加 🔒 + 「只读」,**不渲染任何动作按钮**;`title` 说明「平台共享技能,全员只读,不可停用/删除/覆盖」。→ 与后端 `assertNotShared()` 的 409 双向一致(前端不给出会失败的按钮,后端仍然兜底)。 ### 2.4 上传面板(`.bp-upload`) | 元素 | 交互 | |---|---| | 虚线投放区 | `border: 1px dashed T.border`,r10,`padding: 18px`,居中;文案「把 .zip 拖到这里」+ 次行「技能包需含 `SKILL.md`(name / description)」 | | 「选择文件」 | `input[type=file][accept=".zip"]` 隐藏 + `