Files
dsh_shenxian/dsh-server-docs/06-工作台UI规范.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

20 KiB
Raw Blame History

短视频工作台 UI 规范

【本工作区约定 · 2026-09-10 入库 · 适用范围 2026-09-10 明确】 地位:关键文档 / 强制基线——本工作区内我开发的一切前端页面必须遵循本文档的 Token、组件与交互约定;冲突时以本文档为准。 适用范围(用户明确):除「dsh 自带功能」与「dsh 自带插件页面」外,其余所有功能插件开发(含其 client 面 section)、门户插件、门户页面(desktop / admin / skills / plugins 等)一律参考本规范 + 使用 UI skill。 即:dsh 官方 UI 与官方插件渲染的部分不在约束内(不改它们),我自己写的页面/插件全部在约束内。 权威源:E:\ProgramData\AI技能\mcn-short-video\project\短视频脚本创作\V1.0\mcn-work-shop\docs\工作台UI规范.md(源侧更新后回拷本文件) ⚠️ 该路径 2026-09-12 实测已不存在(glob 无结果;E:\ProgramData\AI技能\mcn-short-video\ 目录仍在,其下已无 工作台UI规范.md)→ 本副本当前即唯一有效版本。源侧若恢复,需重新建立回拷关系并补同步。 UI skill(按场景选对,用错场景等于没用):开发/改前端页面时必须加载并使用对应技能做视觉判断 ——

  • impeccable → 本工作区绝大多数页面(工具类后台、设置面板、管理页、表单、空态)。用 Operate 模式。它的描述明确覆盖 settings / empty states / a11y / 信息层级 / 动效,与本规范同向。 ⚠️ 它自带的机械检测器未随包提供(scripts/detect.mjs 会报 bundled detector not found)→ 改为人工逐条过 reference/craft-floor.md 的 Verify 清单与 Refuse 禁令(2026-09-12 实测的确抓出了真问题,见下)。
  • taste-skill(frontmatter name 为 design-taste-frontend)→ 仅用于落地页 / 作品集 / 整站重设计。 ⚠️ 它自述「Not dashboards, not data tables, not multi-step product UI」 → 本平台的管理页 / 设置面板不在其射程内,不要拿它做这类页面。
  • 冲突时以本文档的实测 Token 数值为准(本文档是实测基线,技能是通用审美)。
  • 位置:E:\ProgramData\.workbuddy\skills\<name>\(2026-09-12 起两个技能已装入全局技能目录,并已过安全审计)。Skill 工具加载不到时直接读文件即可。 改文档规则:UI 决策若伴随"为什么这样改",回写源侧文件并回拷,保持两端一致。

同步时间戳与回拷流程(档案 19 §D8):本文档是回拷副本,权威源见上文"权威源"路径。

  • 最近同步:2026-09-10(入库)→ 之后如有改动请在此追加一行日期;
  • 2026-09-12:修订「UI skill」条款 —— 原表述要求"必须同时使用 impeccable + taste-skill",但 taste-skill 自述不覆盖 dashboards / data tables / 多步产品 UI,与本工作区页面类型不符 → 改为按场景分流(impeccable 管本工作区绝大多数页面;taste-skill 仅限落地页 / 作品集 / 重设计)。
  • ⚠️ 本次未回写源侧:权威源路径(E:\ProgramData\AI技能\mcn-short-video\...\工作台UI规范.md)在本机不存在(2026-09-12 实测 glob 无结果)→ 修订只落在本副本;源侧恢复后需补回拷。
  • 回拷流程:① 改源侧 → ② 回拷到本文件 → ③ 在上一行追加同步日期 → ④ 走文档库对账脚本确认双端一致 (bash scripts/docs-sync-check.sh,0 = 全绿)。

定位:本文件是前端 UI 的设计规范基线(配色 / 字体 / 圆角 / 布局 / 组件 / 交互),供新增页面、调整样式时对照,避免重复踩坑。


1. 设计语言

亮色主题、工具类后台风格。三条主线:

  1. 信息密度优先——表格/列表是主体,行高紧凑(表格 td 7px 10px,字号 15px)。
  2. 克制配色——蓝(#2f6fed)作主色承担交互反馈,橙(#e8590c)只用于强调数值/警示,其余靠灰度拉开层次。
  3. 轻量质感——圆角 8/12px、阴影 0 1px 3px rgba(0,0,0,.08),不用重投影和渐变。

2. 设计 Token

2.1 色彩

变量 值 用途
--bg #f5f6f8 页面底色
--panel #ffffff 卡片/表格/弹窗面板
--border #e3e6ea 统一描边
--text #24292f 主文本
--dim #6b7280 次要文本、说明、占位
--primary #2f6fed 主色(按钮/激活态/链接 hover)
--primary-soft #eaf1fe 主色浅底(hover 行、激活 tab 底)
--accent #e8590c 强调(榜单得分、警示)
--danger #d93026 危险操作(删除)
--shadow 0 1px 3px rgba(0,0,0,.08) 卡片/顶栏阴影

扩展色(未进变量,直接写在规则里)

场景 值
表头底色 #f6f8fa
链接蓝 #4a90e2
主色 hover 深一档 #2a5fd0
图标垂直微调 vertical-align: -0.18em
文件类型色 md #8250df / json #cf222e / xlsx #1a7f37 / txt·csv #57606a
半透明主色底 rgba(47,111,237,.07)(首页统计条)、.16(分隔线)

功能图标亮色映射(未列出的跟随 currentColor): trending-up 橙 · users 蓝 · pen 粉 #ec4899 · flask 青 #0ea5e9 · lightbulb 黄 #d99a2b · refresh 青绿 #0f7f8f · chart 玫红 #d6336c · wrench 灰 #5b6470

2.2 字体

font-family: "Microsoft YaHei", "PingFang SC", "Segoe UI", sans-serif;
font-size: 16px;  /* body 基准 */

代码/JSON 预览:Consolas, monospace。

字号阶梯(实测归纳,不是硬 token):

级别 字号 用例
特大 26px / 21px 首页统计数字、面包屑主标题
标题 18px / 17px 弹窗标题、区块标题、卡片标题
正文 16px / 15px 正文、按钮、表格、输入框
次要 14px / 13px 说明文字、数值列、徽章、tab(小)

2.3 圆角 / 阴影 / 层级

类型 值 用例
圆角 6px 小控件(btn-sm、侧栏切换组、树节点)
8px 默认(按钮、输入框、表格容器、信息卡、评分块)
10px 徽章、榜单容器、卡片
12px 大卡片(首页 data-card、func-card、统计条)
14px 弹窗
阴影 var(--shadow) 卡片、顶栏
0 8px 30px rgba(0,0,0,.18) 弹窗
0 4px 12px rgba(47,111,237,.15) 卡片 hover(主色扩散)
z-index 10 顶栏 / 100 弹窗遮罩 / 200 Toast 表头 sticky 为 1

2.4 间距

没有 token 化,实际使用序列:2 / 4 / 6 / 8 / 10 / 12 / 14 / 16 / 18 / 20 / 24px。常用组合:

  • 卡片内边距 16px 18px;信息卡 14px 16px;表格单元 7px 10px;表头 8px 10px
  • 页面外边距 20px 24px;首页/详情内容 24px;区块间距 14~24px
  • 工具条内部 gap 8~10px;表单行 gap 10px

3. 布局框架

┌─ header.topbar(55px,sticky,白底 + 底部 1px 边框 + shadow,z-index 10)
│    brand(logo) · crumbs(面包屑) · top-actions(首页/帮助/设置,右侧)
├─ main#view  height: calc(100vh - 55px)  overflow: auto   ← 唯一滚动容器
│    .home(max-width 960) / .page(max-width 1440)
└─ 弹窗层 .modal-mask(fixed inset 0,z-index 100)

滚动(坑点,勿改)

规则 原因
html, body { overflow: hidden } 消除顶栏 1px 边框引发的第二层滚动条
main#view { height: calc(100vh - 55px) } 55px 是实测值(padding 20 + 行高 34 + 边框 1)。写成 54px 底部会溢出 1px,触发第二层滚动条
长内容滚动交给内部容器(.rw-sec-body、.rw-content、.file-tree) 保持页面级不滚

内容宽度:首页 .home max-width 960px;功能页 .page max-width 1440px(padding 20 24);弹窗 .rw-modal 宽 min(1600px, 100% - 100px)、高 calc(100vh - 60px)。


4. 组件规范

4.1 按钮

类 外观 用例
.btn 白底 + border + 6px 14px + r8 + 15px;hover 边框/文字变 primary;:disabled opacity .5 默认
.btn-primary primary 底白字;hover #2a5fd0 主行动
.btn-ghost 透明底 + border 次要/弹窗内
.btn-danger danger 系 删除
.btn-sm 3px 10px / 14px / r6 表格功能列
.btn-icon 5px 9px,纯图标 图标按钮
.btn-busy primary-soft 底 + primary 字 + opacity .8,内含图标旋转动画 异步进行中
.btn-import 白底 + #d0d4da 边 + #495057 字 保存类(浅灰底深字)
.btn-view 白底 + primary 边 + primary 字 查看类

功能列成对出现时:保存=浅灰底深字(.btn-import),查看=白底蓝字(.btn-view),靠底色深浅区分而非都用蓝。

顶栏三按钮用 .ghost(白底 + border,6px 14px,r8)。

4.2 输入框

.input(6px 10px,min-width 200px)· .input-select(6px 8px,筛选下拉更窄)。均 1px border + r8 + 15px + 白底。

4.3 表格

.table-wrap  overflow auto + 1px border + r8 + 白底
.table       width 100%, border-collapse collapse, 15px
.table th    sticky top 0, 背景 #f6f8fa, 8px 10px, 600, nowrap, z-index 1
.table td    7px 10px, nowrap, 底部 1px border
tbody tr:hover  背景 var(--primary-soft)
修饰类 作用
.num(td/th) 右对齐 + font-size: 13px + font-variant-numeric: tabular-nums(等宽数字,必须加,否则数字跳动)
.th-wide 数值列表头 min-width 90px + 右对齐
.sortable cursor pointer,hover 变 primary
.cell-content max-width 220px + 省略号
.cell-loc 账号定位列缩窄 110px
.th-act / .cell-act 功能列居中
.title-plain 视频标题:默认同正文色,hover 才变蓝+下划线(避免整列花掉)
.link / .link.strong #4a90e2;.strong 加粗

列宽策略(关键)

场景 做法
榜单表格 .table-rank 必须 table-layout: fixed——auto 布局下 100% 表格的富余空间会按比例摊薄,min-width 压不住;fixed 下 width 才是硬约束,账号列自动吃剩余宽度
普通数据表格 auto 布局,靠 .cell-content max-width + 省略号收口

行高对齐(关键):榜单三个 tab 的行高必须统一,否则切换时整表跳动——

  • 表格 .table-rank td { height: 42px }(功能列 .btn-sm 撑到 42px,其他 tab 原本只有 34.7px)
  • 首页预览 .rank-preview-list .rank-row:not(.rank-head) { min-height: 40px }

4.4 Tab(两套并存,注意覆盖)

位置 样式
顶栏 胶囊式:r8,hover 浅蓝底蓝字,active 蓝底白字 + 600
数据页(重新定义同名 .tabs/.tab) 下划线式:无背景无圆角,border-bottom: 2px transparent + margin-bottom: -1px(压住容器分隔线),active 蓝字 + 蓝色下划线

⚠️ 同名类二次定义,数据页规则在后会覆盖顶栏规则。新增页面若在下划线式区域用 .tab,拿到的是下划线式;需要胶囊式请另起类名。

榜单 tab .rank-tab:r8 8px 0 0(上圆角),active 浅蓝底 + 蓝字 + 底部 2px 蓝线。 首页预览里需 align-self: center(全局 .rank-tabs 用 stretch,会让 tab 组相对兄弟控件偏上约 5px)。

4.5 卡片

类 规格
.data-card(首页入口) r12,16px 18px,shadow,hover 上浮 translateY(-2px) + 蓝边 + 0 4px 12px rgba(47,111,237,.15),transition .15s
.info-card(信息区) r8,14px 16px,margin-bottom 14px;标题 15px/600 + 底部 1px 分隔线
.recent-card(脚本快捷卡) r10,14px 16px,min-height 172px(保证网格等高),hover 蓝边 + 淡影
.func-card r12,16px 18px

4.6 徽章 / 标签

.badge:2px 8px,r10,13px。

  • .badge-signed 浅蓝底蓝字(签约/内部)
  • .badge-ext #fff4e5 底 + accent 橙字
  • .rw-ver-new primary 底白字 r9 12px(最新版本标记,附在版本条最右)
  • .recent-card-score 12px/600 r10

4.7 弹窗

.modal-mask  fixed inset 0, rgba(0,0,0,.35), flex 居中, z-index 100
.modal       白底 r14, 22px 26px, width 520px, max-width 92vw, shadow 0 8px 30px
.req-modal   760px × min(840px, 92vh)   /* AI创作 */
.rw-modal    min(1600px, 100% - 100px) × calc(100vh - 60px)  /* 脚本对比/查看 */

⚠️ .modal-mask[hidden] { display: none } 必须显式写——.modal-mask 是 display:flex,会覆盖 HTML hidden 属性导致弹窗关不掉。

对比弹窗三段式(.rw-*):.rw-stack(flex row, gap 12)→ .rw-sec(flex 1, min-width 0, r8, overflow hidden)→ 内分 .rw-sec-head(8px 12px,白底,底部边框)/ .rw-sec-body(12px 14px,overflow auto)/ .rw-sec-foot(min-height 46px,右对齐,顶部边框)。

.rw-sec-foot 的 min-height 是为了空 foot(源脚本列占位)与其他列等高,不能删。

4.8 Toast

固定在底部居中(bottom: 32px,translateX(-50%)),深底 #24292f 白字,10px 18px r8,z-index 200,显示 1.8 秒后自动消失。用于复制成功、保存结果等轻量反馈;失败重试类提示走行内 .toolbar-note / .empty。

4.9 空状态

.empty-state:纵向居中,padding: 56px 20px,gap 10px——图标(dim + opacity .45)→ 标题(17px/600)→ 说明(14px/dim)→ 行动按钮(margin-top 10px)。 轻量占位可用 .empty(padding: 30px,居中,dim)。

4.10 分页 / 批量

.pager 右对齐(gap 12,padding: 12px 4px);.list-footer 用 space-between 把批量操作条放左、分页放右;批量条 .list-batchbar 出现时与分页同行。

4.11 评分条

.score-row:名称 .score-name 固定 88px → 进度条 .score-bar(flex 1,height 6px,r3,底 #eef1f5)→ 分值 .score-val 固定 36px 右对齐。三档宽度固定,保证多行对齐。

4.12 图标

svgIcon(name, size = 16, color)  ;生成 class="ic" 的 SVG
  • Lucide 风格:viewBox 0 0 24 24,stroke-width: 2,fill: none,stroke: currentColor
  • 静态 HTML 用 <i data-icon="home"> 占位,initIcons() 统一替换
  • .ic { vertical-align: -0.18em; flex-shrink: 0 } ——垂直微调 -0.18em,去掉会和文字基线错开

5. 关键交互约定

约定 说明
数值一律右对齐 + 等宽数字 text-align: right + font-variant-numeric: tabular-nums,列表头同步右对齐
字段对齐用 grid 列轨道共享 账号信息/视频详情的多行字段,用同一个 grid 让列跨行对齐;每行独立 flex 会因内容长短把后续字段顶偏(R44/R45 教训)
超宽字段移出共享网格 如「账号定位」整行留在网格内会把 4 条轨道撑成等宽(各 232.5px),右侧大片留白
滚动联动单向 AI 脚本列滚动驱动分镜列对齐,不做双向耦合(双向会互相抖动)
长文本截断三件套 overflow: hidden + text-overflow: ellipsis + white-space: nowrap,并挂 title 显示全文
hover 才提示可点 列表中的标题类文本用 .title-plain(默认同正文色,hover 变蓝+下划线),避免整列都是蓝字

6. 已知坑与规避

# 坑 规避
1 main#view 写成 calc(100vh - 54px) → 底部溢出 1px,出现第二层滚动条 用 55px(padding 20 + 行高 34 + 边框 1)
2 榜单表格用 auto 布局 → 列宽被富余空间摊薄,min-width 失效 加 .table-rank { table-layout: fixed } + 显式列宽
3 榜单三 tab 行高不一致 → 切换时整表跳动 表格 td 固定 42px;首页预览行 min-height 40px
4 .modal-mask 的 display:flex 覆盖 hidden → 弹窗关不掉 显式写 .modal-mask[hidden] { display: none }
5 数值列没加 tabular-nums → 滚动/翻页时数字左右跳 数值列统一加
6 图标与文字基线错开 .ic 的 vertical-align: -0.18em 不能删
7 首页 .rank-tabs 相对兄弟控件偏上 ~5px 预览场景加 align-self: center; margin-bottom: 0

7. UI 改动的生效链路与验收(2026-09-12 实战确立,所有 UI 改动必走)

起因:v0.2.6 把「功能管理」列表改卡片后,磁盘、服务端、实例三处都验过是新的,用户却一直看到旧 UI —— 排查耗了 40+ 步。 根因不是"改错地方",而是链路上有两道时序/缓存:client bundle 只在实例启动时加载;而浏览器按 URL 里的 rev 决定要不要重新拉。 ⇒ 为让所有 UI 改动能被稳定验收,确立以下机制。

7.1 生效链路(改完到"用户看见",要过四关)

关 环节 关键条件
① 改源码 → npm pack → 打包 必须升版本号 —— 同版本改内容会让 pnpm 判定"无需更新",白改
② 铺到用户 profile(ensure-biz-plugins.cjs 等) 改完立刻生效(磁盘层)
③ 实例加载 bundle 必须重启实例 —— client bundle 在实例启动时加载;不重启则实例内存里仍是旧代码
④ 浏览器取到新代码 由 URL 里的 rev(内容 hash)决定;平台已加 no-cache 兜底

7.2 平台侧机制(2026-09-12 落地,勿删)

  • Cache-Control: no-cache:平台代理层对实例的 /plugins/ 响应统一加此头 —— 允许缓存但每次回源校验,bundle 一变浏览器就能拿到新的。
    • 实现位置:src/supervisor/proxy.ts,搜索 平台缓存治理
    • ⚠️ /assets/ 另有 nginx location ~* ^/assets/ 的 expires 30d 会覆盖此头 —— 但 /assets/ 是 dsh 构建产物(文件名带 hash),30d 合理,无需处理
  • rev 由 dsh 官方按内容计算(dsh-client-modules:framedHash("combo", [sourceBytes, sourceMap])),平台不得改(红线 R2);且 dsh 会拒绝 rev 不匹配的请求(官方注释:mismatched revisions are rejected instead of serving newer bytes)—— 这恰好成了"实例是否已换新"的判据。

7.3 验收口径(三段式,缺一不可)

查什么 怎么查 期望
磁盘 grep -c <新代码特征> <profile>/node_modules/<pkg>/lib/*.js ≥ 1
实例已加载 抓实例页 HTML,比对 URL 里的 rev 与改前不同(变了 = 实例加载的是新内容)
服务端返回 用 HTML 里那条完整 bundle URL 请求 HTTP 200 + 含新代码特征

7.4 四个常见误判(都是本次踩过的)

  1. 拿单包 URL 请求 → 404(/plugins/?? 只认完整包列表,不是单包)
  2. 去服务器上 /opt/dshs/poc/** 的源码副本里 grep → 那是源码、不是实例读取路径(实例读 profile/node_modules)→ 会误判成"根本没改"
  3. 只看"实例是否重启过" → 还要看 HTML 里 rev 是否真的变了
  4. 取实例页 HTML 漏参数 → 必须 curl -L --compressed -c jar -b jar -H "Accept: text/html"(缺一即被 gzip 拦截,拿到空内容)
  5. client bundle 的 inject 写成数组 → 必须是函数(原包写法 inject: () => ({}),返回「要注入的服务映射」对象)。写成数组 ["@deepseek-ai/dsh-client-runtime", …] 会被 dsh 理解为「在等这些服务」⇒ 条目永久 pending:
    web boot: 1 entry did not activate
    <pkg>: pending (waiting for services: @deepseek-ai/dsh-client-runtime, …)
    
    ⚠️ 这个错只在浏览器里暴露——服务端日志显示 [xxx] loaded 一切正常,inject 类型错误完全看不出来(2026-09-12 实测)。
  6. 整合包的 client bundle 必须注册「包名自己」 → 拼接多个原包时,它们各自 load({id: "<原包名>"}),包名本身不会自动注册;dsh 按包名找 ⇒ 报 loaded without registering "<包名>" via __ModuleLoader__.load。 正确做法:子包的 load 改为「收集」,再由本包 load({id: 包名, factory}) 统一注册,并在 factory 内驱动子包 factory(否则子包 UI 永不注册)。