Files
xiaotutongxue/frontend-pc/.agents/skills/code-css/SKILL.md
T
lovife 623e6393d5 fix(pc): 弹窗遮罩改毛玻璃并清掉 9 处提示折行
遮罩底色被 Naive 写死在 css-render 里(Modal / Drawer 主题没有 mask token,
themeOverrides 改不到),只能上 CSS;加 backdrop-filter 后遮罩后面的页面糊掉、
弹层本体仍是纯白,NModal / NDrawer / useDialog 共用 .n-modal-mask 一条规则覆盖。

折行的根因是格子宽度写死(半宽 264px / span 8 192px / 定宽 160px),与窗口宽度无关:
能压短的压短;「文件名预览」那处是 .n-form-item-blank 属于 flex 行、把提示挤成 3 行,
改成包一层块级容器;材料预览失败的占位在 108px 小格里曾折 3 行且被裁掉。

约定写进 frontend-pc/AGENTS.md 第 11 条与 code-css skill;实测与刻意不改的清单见
docs/交接摘要.md 的 §一·四十二。
2026-10-14 15:34:40 +08:00

8.5 KiB
Raw Blame History

name, description
name description
code-css SCSS 写法、样式作用域与全局样式的边界。编写或修改 .vue 的 style 块、.scss 文件时加载本技能。

样式规范(SCSS + Naive UI)

⚠️ Stylelint 已随 2026-10 重搭移除(pnpm lint:style 不存在了)。样式只受 ESLint 与人工约定约束。

放哪里

场景 位置
组件的具体样式 该 SFC 的 <style scoped lang="scss">
跨页面的版面基线(背景、卡片外观、卡片间距、滚动、分页器容器、区块标题) src/styles/index.scss(只放跨页面的东西)
组件外观的主题变量(Card / Button / DataTable 的描边、圆角、阴影) src/App.vue 的 themeOverrides

设计基线(2026-10 卡片式改造后:页面只引用,不再各写一套)

src/styles/index.scss 的 :root 里定义,页面直接 var():

变量 值 用途
--xt-header-h 56px 顶栏高度(.app-shell__header 与 .page-card 高度算式共用)
--xt-content-pad 16px 内容区内边距(同上,两处共用,别写死数值)
--xt-gap 16px 卡片/区域之间的间距
--xt-bg #f2f4f7 页面底色(卡片要靠它才浮得起来)
--xt-card-border / --xt-card-border-weak #e5e8ee / #eef1f5 卡片描边 / 嵌套卡片描边
--xt-card-radius 10px 卡片圆角
--xt-card-pad-x / --xt-card-pad-y 18px / 16px 卡片内容区左右 / 上下内边距(别在页面里写死,见下)
--xt-band-bg #f7f8fa 浅灰色带:卡片头 / 表头 / 卡片内的子格子
--xt-text / --xt-text-2 / --xt-text-3 #1f2229 / #667085 / #98a2b3 主 / 次 / 弱文字
--xt-mask-bg / --xt-mask-blur rgb(16 24 40 / 26%) / 8px 弹窗遮罩的底色与模糊半径(见下节)

⚠️ 卡片内边距:不要让页面自己写

Naive 的规则是 cB("card-content", "padding: 0 var(--n-padding-left) var(--n-padding-bottom) …") 外加 c("&:first-child", "padding-top: var(--n-padding-top)") —— padding-top 只给"内容区是长子"(也就是没有卡片头)的卡片。 所以有标题的卡片,内容区 padding-top 是 0,第一行内容直接贴在标题下面那条灰条上。

全局 .n-card.n-card > .n-card-content { padding: var(--xt-card-pad-y) var(--xt-card-pad-x) } 已经统一处理掉了。但它是普通样式,会被内联样式盖掉 —— 页面里写 :content-style="{ padding: '12px' }" 会让那张卡片比邻居紧一圈 (而且那个写法只解决左右,不管上面这件事)。要调内边距就改 --xt-card-pad-x/y。

📌 排查手法:getComputedStyle(card.querySelector(':scope > .n-card-content')).paddingTop 应当是 16px;是 0 说明没匹配上,是别的值说明有内联样式或更具体的选择器在盖。

现成的全局类(优先复用,不要另起名字):

类 用途
.page-plain 普通(非列表)页根节点:flex 列 + --xt-gap 间距
.page-card + .page-card__body 列表页专用高度链:卡片撑满内容区 → 表体自滚 → 分页器钉底
.toolbar / .toolbar--plain 筛选与动作行(自带下分隔线 / 去掉分隔线)
.toolbar__hint 工具条里的灰色小字提示
.section-title + .section-title__note 卡片内部次级区块标题(左侧蓝色小竖条 + 右侧灰色说明)

卡片式的三条规矩

  1. 卡片外观是全局给的:.n-card.n-card 统一白底 + 描边 + 轻阴影 + 卡片头浅灰色带 (权重写成 .n-card.n-card 是因为 naive 的样式由 css-render 运行时注入,先后顺序不保证)。 页面不要再自己给卡片写边框/阴影。
  2. 卡里不要套卡:卡片内部要分区时,用浅灰底(--xt-band-bg)+ 弱描边(--xt-card-border-weak) 的小格子,或 .section-title + 1px 分隔线。嵌套的 NCard 已被全局降级为无阴影 + 无色带, 但布局上仍然容易糊成一片。
  3. 区域间隔靠"色带 + 描边",不是靠加大 margin:卡片之间 --xt-gap,卡片内区块 12~20px。 上一版的教训是把卡片写成 :bordered="false" + 内容区白底 → 白卡片浮在白底上,整页没有任何边界。

弹窗遮罩(毛玻璃):只能用 CSS,别去找主题变量

遮罩层(弹窗背后那层灰黑)走毛玻璃:styles/index.scss 里一条规则覆盖 NModal / NDrawer / useDialog() 三种弹层(useDialog 内部就是 NModal)。

.n-modal-mask.n-modal-mask,
.n-drawer-mask.n-drawer-mask:not(.n-drawer-mask--invisible) {
  background-color: var(--xt-mask-bg);
  backdrop-filter: blur(var(--xt-mask-blur)) saturate(130%);
  -webkit-backdrop-filter: blur(var(--xt-mask-blur)) saturate(130%);
}
  • ⚠️ 为什么不能用 App.vue 的 themeOverrides:Naive 把遮罩色写死在 css-render 里 (modal/src/styles/index.cssr.mjs 的 modal-mask = rgba(0,0,0,.4)、 drawer/src/styles/index.cssr.mjs 的 drawer-mask = rgba(0,0,0,.3)), Modal / Drawer 的主题里没有 mask token —— 主题变量改不到它。
  • ⚠️ 双类名:与 .n-card.n-card 同一招(css-render 运行时注入 <head>,先后顺序不保证,把权重提到 (0,2,0))。
  • ⚠️ .n-drawer-mask--invisible(show-mask="transparent")必须排除:它是"看不见的遮罩", 被染色就变成"看得见的挡板"。.n-modal-mask 没有对应的隐藏变体。
  • 自查:getComputedStyle(document.querySelector('.n-modal-mask')).backdropFilter 与弹层本体的 backgroundColor(应为 rgb(255, 255, 255))。

按钮的可见性

  • 页面主操作(新建 / 导入 / 批量通过…)用 type="primary",默认尺寸(不要 size="small")。
  • 同一行内控件尺寸必须一致:一行里混用 small(28px) 与默认(34px) 会参差不齐。 工具条统一默认尺寸;表格行内动作保持 text + size="small"。
  • 默认按钮的描边已在 App.vue 里加深(#d0d5dd)—— 原来用的 common.borderColor(#e0e0e6) 在白色卡片上几乎看不见,这是"按钮不明显"的主因。
  • ⚠️ naive 的按钮边框画在子元素 .n-button__border 上,不是根节点; 用 getComputedStyle(btn).borderTopWidth 检查会得到 0px,别据此判断"边框没生效"。

覆盖 naive 内部类之前:先确认真实类名

⚠️ naive 的类名不是一套统一规则,同一个组件里两种命名混着用:

真实类名 由什么生成
.n-card-header / .n-card-content cB("card-header") / cB("card-content") —— 单短横
.n-button__border / .n-button__content cE("border") / cE("content") —— 双下划线

所以"想当然按 BEM 写"会静默失效::deep(.n-card__content) 匹配不到任何元素, 不报错、不影响编译,只是那条样式什么也没做。

2026-10 实际踩到:.page-card 的高度链第一环(卡片内容区要是能撑满的 flex 列) 就写在 :deep(.n-card__content) 里 —— 结果「操作日志」的表格体高度算成 0, 行在 DOM 里、数据也查得到,屏幕上一行都看不见。

正确做法:在 DevTools 里选中那个元素,看它实际的 class;或者去 node_modules/naive-ui/es/<组件>/src/styles/index.cssr.mjs 里搜一眼。 顺带:.n-card-content 与 .n-card-header 这两个名字已经被 styles/index.scss 用到, 改卡片相关样式时以那里为准。

约定

  • class 用 kebab-case;同一块的子元素用 块__元素 风格(task__tabs、join__hint), 与现有页面保持一致即可,不要引入新的方法论或工具链。
  • 颜色优先用 Naive UI 的 CSS 变量 / 主题变量,不要写死 Arco 时代的 --arco-* 变量(已不存在)。
  • 选择器保持扁平(≤2 层),避免 !important;确实要覆盖 naive 内部类时加注释说明原因。

踩过的坑(照抄现有写法即可)

  1. 列表页卡片要能自己滚:分页器被裁出可视区是本项目反复踩的问题 —— 列表页统一用 styles/index.scss 里的 .page-card 容器(卡片撑满内容区 → 表体自己滚 → 分页器钉在卡片底部)。
  2. 页面根节点用 NPagination/NDataTable 时别自己写 overflow: hidden,会把分页器裁掉。