Files
dsh_shenxian/dsh-server-docs/04-调整方案/31-插件管理页双Tab与说明为主视觉.md
T

102 lines
8.1 KiB
Markdown
Raw Normal View History

# 31 · 插件管理页双 Tab + 说明为主视觉 + 目录字段用全
- 日期:2026-09-11
- 触发:用户提问「**导入到候选池是什么意思**」+「dsh 官方推荐插件 和 手动添加和管理插件 能否分两个 tab 展示」+「**官方插件优先展示应该是插件说明或中文名称**,需要一眼知道这个插件是干什么的」
- 状态:**✅ 已实施并视觉验证(commit `103471e`)**
## 一、需求与背景
三个问题指向同一件事:**档案 29 交付的插件管理页,信息层级与术语都没站在「看得懂」的角度设计。**
1. **术语费解**:「导入到候选池」中的「候选池」是内部叫法,admin 本人看不懂 —— 说明页面缺少对「投放 ≠ 生效」的解释。
2. **IA 混在一起**:官方目录挑选、手工上传、已投放管理三件事挤在一页,纵向下堆。
3. **信息层级错**:表格首列是插件名(`dsh-market/dsh-market` 这类技术标识),说明被挤到最后一列且单行截断 —— **看不出插件是干什么的**。
## 二、调研:官方目录还有哪些字段没用
拉 `plugins.json` 逐字段核对后发现,档案 29 只用了 7 个字段,**漏了 3 个**:
| 字段 | 覆盖 | 是否用上 | 说明 |
|---|---|---|---|
| `description.zh` | **3408/3408(100%)** | 部分(末列截断) | 中文说明,**平均 83 字** |
| `category` + 顶层 `categories` | 100%(23 类) | ❌ 只显示原始 id(`ui`) | 顶层 `categories = { id: { en, zh } }`,**官方只在这里给分类中文名**(如 `ui`→UI 增强、`tools`→工具与能力) |
| `page` | **3408/3408** | ❌ 未用 | 官方详情页 `awesome-dsh-plugin.com/p/<owner>/<name>/`(完整介绍 + 截图 + 评论) |
| `screenshots` | 594/3408(17.4%);可导入子集 421/1792(23.5%) | ❌ 未用 | 截图 URL 数组 |
| `added` | 3408 | ❌ 未用 | 收录日期(最早 2026-08-13 / 最新 2026-09-08)—— 跨度过窄,做「新收录」标记无意义,故未用 |
| `stars` / `downloads` | 1586 / 1653(可导入子集) | ✅ | — |
| **`tags` / `keywords`** | **0(不存在)** | — | **上游没有标签体系**,分类是唯一 taxonomy(23 类) |
## 三、实现(commit `103471e`)
### A. 双 Tab(`web/portal.html` → `#/plugins`)
- 样式按 `06-工作台UI规范.md` §4.4 **下划线式**(无背景无圆角,`border-bottom: 2px`,active 蓝字 + 蓝下划线,`margin-bottom: -1px` 压住分隔线)。
- **类名用 `.pg-tabs` / `.pg-tab`**,刻意避开全局 `.tab` —— 06 明确警告 `.tab` 被顶栏(胶囊式)与数据页(下划线式)二次定义,复用会拿到不可预期的样式。
- Tab:**「官方推荐插件」** / **「手动添加 / 管理」**(后者带已投放计数徽章)。
- **深链**:切 tab 用 `history.replaceState` 写 `#/plugins/<tab>`(**不触发 `hashchange`**,因此不会重跑 router);初始 tab 读 hash 后缀。现有 `parseHash()` 取 `parts[0]`,`#/plugins/manual` 依旧路由到 plugins,无需改路由。
### B. 说明为主视觉(用户核心诉求)
白名单表格首列由「插件名」改成 **「插件说明」**:
| 行 | 内容 | 样式 |
|---|---|---|
| 主行 | **中文说明**(2 行截断,`-webkit-line-clamp: 2`,`title` 悬停看全文;为空时回退显示插件名并加粗) | 14px / 正文色 |
| 副行 | `插件名 · npm包名 @ 版本 · ★N` | 12px / dim |
分类列改显示**中文分类名**;新增 **「详情 ↗」** 外链列指向官方 `page`;保留 `下载量`(热度是排序主信号),★ 下移到副行。
### C. 容器宽度
6 列信息量下表格**横向溢出 74px**。修法:给插件页单独放宽内容容器 —— `router()` 给 `#view` 打 `page-<name>` 类名,`#view.page-plugins { max-width: 1200px }`,**其他页仍 960 不受影响**。实测 `scrollWidth/clientWidth = 1105/1105`(无溢出)。
### D. 术语落到页面
「已投放插件」卡片加说明:
> **投放 ≠ 生效。** 这里只是把插件**收录到平台**,对任何用户都不生效;用户要真正用上,需在自己实例的「设置 → 功能插件」里勾选启用 —— 启用时平台才会把插件装进他的环境,重启实例后生效。
按钮文案 `导入到候选池` → **`导入到平台`**,确认弹窗同步改口径。
### E. 后端(`src/web/routes/whitelist.ts`)
- 解析顶层 `categories` → `categoryLabels`(id→中文名),随缓存留存;条目新增 `categoryLabel` / `hasShots`。
- `GET /api/plugins/whitelist` 的 `categories` 由 `string[]` 改为 **`[{id, label, count}]`**(按条目数降序),供前端下拉直接用中文 + 计数。
- **`CACHE_VERSION` 2 → 3**(新增字段必须 bump,否则 TTL 内旧格式缓存被复用 —— 档案 29 踩过这个坑)。
## 四、改动文件
| 文件 | 改动 |
|---|---|
| `src/web/routes/whitelist.ts` | 解析分类中文名映射;条目加 `categoryLabel`/`hasShots`;`categories` 改对象数组;`CACHE_VERSION` → 3 |
| `web/portal.html` | 双 Tab + `.pg-tab` 样式;白名单表格列重排;`page-plugins` 容器放宽;`router` 打页面类名;「投放 ≠ 生效」说明与文案 |
## 五、验证(独立 headless Chrome,2026-09-11)
**方法**:起**独立 headless Chrome**(`--headless=new --remote-debugging-port=9223 --user-data-dir=<独立 profile>`),CDP 直连(`BU_CDP_URL`),注入临时 admin session cookie(`poc-ui`,用后即删)。**不碰用户日常浏览器、无授权弹窗**。
| 用例 | 结果 |
|---|---|
| 后端 `GET whitelist?onlyImportable=1&category=ui` | 200;`categories[0..5]` = UI 增强(565) / 工具与能力(457) / 开发与运行时(262) / 会话与消息(215) / 工作流与自动化(207) / 用量与计费(187);条目带 `categoryLabel`/`page`/`hasShots` |
| Tab 渲染与切换 | `tabs=2`;点「手动添加 / 管理」→ URL `#/plugins/manual`、pane 互斥切换正确 |
| 深链 | 直接访问 `#/plugins/official` 落在正确 tab |
| 表格布局(1440×1000) | `scrollWidth/clientWidth = 1105/1105` → **无横向溢出**;6 列表头齐全 |
| 首行内容 | 说明为主 + 副行 `dsh-market · dshmarket @ 1.45.0 · ★ 3408` + 分类「插件市场与管理」+「npm 预构建」+ 383506 + 「详情 ↗」 |
| 静态文件即时生效 | 改 `portal.html` **无需重启**(服务启动时间未变);后端改动才 build + restart |
| 内联 JS 语法 | `node --check` 通过 |
截图:`p-official.png` / `p-manual.png`(本机临时目录,未入库)。
## 六、事故/踩坑记录
1. **`browser-harness` 反复弹 Chrome 授权**(用户明确抱怨):根因是**我每次都用 `python -m browser_harness.run` 直接起进程,绕过了 wrapper 脚本** —— wrapper 会设 `BH_RUNTIME_DIR_SHARED=1` 让 daemon 常驻复用;绕过它则每次新建连接 → Chrome 每次弹窗。**修法:起独立 Chrome 实例 + 固定调试端口 + `BU_CDP_URL` 直连,此后零弹窗。**
2. **`Emulation.setDeviceMetricsOverride` 按 target 生效**:先 `new_tab` 再设覆盖才对;设在旧标签上、再开新标签 = 覆盖丢失(截图只有 758×482 宽)。
3. **hash 残留影响验证**:页面停在上次的 `#/plugins/manual`,重载后 `#wlTbody` 不存在 → 元素查询返回 null(表现为 `0/0`)。验证脚本必须**显式导航到目标 tab**。
4. **移动 `.bak` 前先查是否被 git 跟踪**(档案 29 教训复用)。
5. **后端字段增删必须 bump `CACHE_VERSION`**(档案 29 教训复用)—— 本次主动执行,未再踩。
## 七、后续(未做,按需)
1. **截图预览**:`screenshots` 覆盖仅 17.4%(可导入子集 23.5%),现已置 `hasShots` 但未展示;若要「图看效果」,可在「详情」列改为悬停出缩略图(覆盖率不高,需评估是否值得)。
2. **描述完整展示**:现在 2 行截断(约 60 字 / 平均 83 字)+ 悬停看全文。若要求「不悬停也全看到」,可加行高(3 行 clamp)或再放宽容器。
3. **「候选池」术语**:已把按钮/说明改成「平台投放」口径,但**内部代码与 API 仍叫 business_plugins / 候选池**;是否统一改名待定。