623e6393d5
遮罩底色被 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 的 §一·四十二。
138 lines
8.5 KiB
Markdown
138 lines
8.5 KiB
Markdown
---
|
||
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`,会把分页器裁掉。
|