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

138 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: code-css
description: 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`)。
```scss
.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`,会把分页器裁掉。