Files
workspace/code/fms/.workbuddy-ai/memory/2026-09-16.md
T
2026-09-16 17:27:04 +08:00

8.5 KiB
Raw Blame History

2026-09-16 工作日志

UI 控件横向内边距拆分(输入型 vs 点击型)

背景:--fms-control-padding-x 原本被 6 处共用,输入框偏宽。

改动:

  • src/theme/tokens.css 新增 --fms-field-padding-x: 4px(输入型控件专用), --fms-control-padding-x: 10px 保留给按钮/标签页等「点击型」元素。
  • 切到 --fms-field-padding-x 的 4 处:ui/input/index.scss(.input-wrapper 单行 + .input-textarea 多行)、ui/select/index.scss(触发器)、ui/date/index.scss(.fms-date-trigger / .fms-range-trigger 共用规则,DatePicker + RangePicker 都覆盖)。
  • 保持 10px:ui/button/index.scss:13、ui/tabs/index.scss:147。

结论:以后调输入框内边距只动 --fms-field-padding-x,不会波及按钮/标签页。

DatePicker 触发器布局重构(清除按钮绝对定位)

问题:.fms-date-suffix 这个 <span> 没有 v-if,空态也占位;外层 gap: 6px 照常生效, 所以 input 右侧总有距离。更糟的是 hover 时清除按钮从 display:none 变 inline-flex, 会挤压 input 造成文字左右跳动。

改法(ui/date/index.scss):

  • .fms-date-trigger / .fms-range-trigger:加 position: relative,删掉 gap: 6px。
  • .fms-date-suffix:改 position: absolute + top/right/bottom: 0,脱离文档流; pointer-events: none,子元素 > * 恢复 auto(避免绝对定位层挡住触发器点击)。
  • .fms-date-input / .fms-range-input:padding: 0(原生元素不留内边距)。

第二版修正(重要):第一版给 input 加了 padding-right: 18px 预留清除按钮位, 导致无值时右侧恒有 18px 空白。改为对齐 antd 的「叠放 + 透明淡入」:

.fms-date-clear {
  display: inline-flex;   /* 常驻 DOM,不再 display:none */
  opacity: 0;             /* 默认透明 */
  pointer-events: none;
  transition: opacity var(--fms-duration-base) var(--fms-ease-standard);
}
.fms-date-trigger:is(:hover, :focus-within) .fms-date-clear,
.fms-range-trigger:is(:hover, :focus-within) .fms-date-clear {
  opacity: 1; pointer-events: auto;
}

组件库通行做法(查证结论,别再自己发明)

Ant Design Element Plus
按钮定位 absolute absolute
隐藏方式 visibility:hidden(Input)/ opacity:0(Picker) 条件渲染
是否预留空间 不预留,靠叠放/淡入淡出 常驻预留(clearable 时 padding+14px,被吐槽)
切换动画 有(opacity 过渡) 无
  • antd Input 用 visibility: hidden 而非 display:none,因为前者保留布局空间,文字不跳。
  • antd DatePicker 的 -clear 与 -suffix 叠在同一位置,hover 时 clear 淡入、suffix 淡出。
  • antd 原生 input 一律 padding: 0,内边距全由外层 wrapper 提供。
  • 结论:正确解法是「绝对定位 + opacity 过渡」,不是「预留 padding」。后者必然造成常驻空白。

实测验证(Playwright 量取真实渲染几何):

  • 未 hover:input padding: 0/0,触发器 296px 内 input 286px,Lgap 5 / Rgap 5,clear opacity:0。
  • hover 后:input 宽度漂移 0.00px、leftGap 漂移 0.00px,clear opacity:1。
  • 即:空间不浪费 + 文字不跳动,两个目标同时达成。

复用经验:本地用 Playwright 验证前端视觉

本项目 fms-vue 没装 playwright;用托管 workspace 的:

  • 安装:C:/Users/Administrator/.workbuddy-ai/binaries/node/versions/22.22.2-2/npm.cmd install playwright(在 C:/Users/Administrator/.workbuddy-ai/binaries/node/workspace)
  • 运行:"C:/Users/Administrator/.workbuddy-ai/binaries/node/versions/22.22.2-2/node.exe" script.mjs
  • 坑:ESM 不支持 NODE_PATH,也不支持裸目录绝对路径,必须写 import { chromium } from 'file:///C:/.../node_modules/playwright/index.mjs'。
  • /demo 组件演示页需要登录态。绕过登录:后端 POST /api/auth/login ({"orgid":"G3HD","userid":"g3soft","password":"g3soft"})拿 token, 用 addInitScript 写入 sessionStorage['fms-login'](形如 {loginInfo:{token,user}})
    • localStorage['fms-user']({userInfo:{orgId,account}}),再进 /demo。
  • 左侧分类要点击「DatePicker 日期选择」才会渲染对应 demo,直接 goto 拿到 0 个触发器。
  • 验证完记得删掉临时脚本和截图。

侧栏改为黑色(深色导航面 + 对比度重配)

需求:fms-layout 侧栏背景改黑,字体与图标颜色要跟着调(否则黑底上看不见)。

关键决策:新增侧栏专用 token,不动全局中性色。 浅色主题下侧栏要黑,但 --fms-surface / --fms-text 是顶栏、内容卡、全部业务页面 共用的浅底配色;直接改全局会让整个主区一起变深。故在 src/theme/tokens.css 的 :root 与 .dark 各加一组 --fms-sidebar-*,只被 AppSidebar.vue / NavMenuItem.vue 引用:

token 浅色 深色
--fms-sidebar-bg #111318 #0b0d11
--fms-sidebar-text #d7dbe2 #ced3db
--fms-sidebar-text-secondary #8b93a2 #838b99
--fms-sidebar-hover-bg rgba(255,255,255,.08) .1
--fms-sidebar-divider rgba(255,255,255,.12) .14
--fms-sidebar-accent-fill color-mix(primary 38%, #000) 46%
--fms-sidebar-accent-edge color-mix(primary 40%, #fff) 46%
--fms-sidebar-logo-bg color-mix(primary 55%, #fff) 45%

踩坑点(下次改深色底必看):默认主色「墨蓝」#0f172b 本身就是近黑。 原激活态写法 color: var(--fms-primary) + background: color-mix(primary 10%, transparent) 在黑侧栏上等于完全不可见。

已改文件:

  • src/theme/tokens.css — 新增上述两组变量。
  • src/layouts/components/AppSidebar.vue — 底色/文字/hover/focus/次级文字/滚动条换成 --fms-sidebar-*;去掉 box-shadow: var(--fms-shadow-right)(阴影落黑底不可见, 反在交界处糊灰边),改 border-right: 1px solid var(--fms-sidebar-divider); Logo 底改 --fms-sidebar-logo-bg + 图标 #111318。 团队下拉菜单(.fms-team-menu-*)保持浅色 token 不变 —— 它在 body 下(Teleport)。
  • src/layouts/components/NavMenuItem.vue — 全部 --fms-text / --fms-secondary / --fms-border 换 --fms-sidebar-*;hover 文字提到 #fff;激活态见下节。
  • src/layouts/DefaultLayout.vue — 侧栏侧分隔条 ::before 提亮 (--fms-sidebar-text-secondary),否则浅色下拖拽线在黑底上看不见。

对比度(底 #111318):菜单文字 12.7:1、次级文字/箭头 5.9:1、Logo 图标 7.2:1。

菜单选中项:主题色实底 + 白字 + 主色亮描边

需求:选中菜单项用主题色做背景、文字白色。

走过的弯路(重要,别再试):第一版按常规模板写成 「background: color-mix(primary 42%, #fff) + color: #fff」(即把主色提亮当浅色底)。 实测白字对比度只有 1.4~2.7:1,全部不合格且远低于 AA 的 4.5:1 —— 因为任何主色与白混到能看清色相的亮度,都已经是浅色(pastel),白字压不住。

结论:黑侧栏上的选中块必须是「深底 + 白字」,不能用「浅底 + 深字」。 方案是把主题色拆成一对 token:

  • --fms-sidebar-accent-fill = color-mix(primary 38%, #000) —— 压暗保留色相, 作为实底,白字对比度 5.1~19.2:1 全部达标(最深/最浅预设都过)。
  • --fms-sidebar-accent-edge = color-mix(primary 40%, #fff) —— 提亮做主色描边。 没有它,近黑主色(墨蓝)的实底与侧栏底只有 1.04:1,选中态整块消失; 有描边后边界可辨度 3.96:1。

即:文字可读性靠填充、选中可辨识性靠描边,二者分离,这样 9 个预设色全过。 提亮版仍用于 focus 描边(NavMenuItem / AppSidebar 的 :focus-visible)。

连带细节:加了 border: 1px 会挤占尺寸导致选中时文案左右跳动, 故 .fms-nav-link 的 padding 由 0 8px 收为 0 7px 补偿(折叠态规则在后、特异性更高, padding: 0 不受影响)。激活项 hover 只轻微提亮填充,不退回普通 hover 的半透明白底。

验证:pnpm lint 无新增问题(唯一 error 是既有 createCellRenderer.old.js 的 no-unused-vars,与本改动无关);pnpm build --outDir dist-verify --emptyOutDir 通过。