137 lines
10 KiB
Markdown
137 lines
10 KiB
Markdown
# 样式与设计 Token 规范
|
|
|
|
> Anthropic 风(warm editorial)设计体系:色板、字体、圆角与反模式清单。代码事实源:`oci-portal-dash/src/theme/tokens.ts`(JS 侧唯一来源)与 `oci-portal-dash/src/assets/main.css`(Tailwind `@theme`)。
|
|
|
|
## 基本规则
|
|
|
|
- 设计 token(色板、圆角、字体、间距)唯一来源是全局 CSS 变量(Tailwind `@theme`),Naive UI `themeOverrides` 从同一份取值;组件内禁止散落硬编码 hex。
|
|
- Tailwind 工具类只用于布局、间距、排版;组件视觉(按钮/输入/表格)交给组件库主题,不用工具类二次描皮。
|
|
- 间距遵循 4px 网格;圆角、阴影、配色遵守下方 token 与反模式清单。
|
|
|
|
## 风格特征
|
|
|
|
- **暖纸色底**:不用纯白,用象牙色 `#FAF9F5` 做主背景,靠 1px 细边框(hairline)和一层灰度差分层,几乎不用投影。
|
|
- **单一强调色**:赤陶橙(terracotta)只出现在主按钮、激活态、品牌标记上,页面 95% 面积是黑白灰米。
|
|
- **小圆角、扁平**:圆角 4–6px 量级;无渐变、无玻璃拟态、无 3D 浮雕、无大面积色块光晕。
|
|
- **排版即装饰**:层级靠字号/字重/留白拉开;图标用细线性风格(lucide/tabler),不用 emoji 和彩色立体图标。
|
|
- **克制的动效**:过渡 150–250ms、只做透明度/位移,无弹跳和视差。
|
|
|
|
## 官方色值(Anthropic 官方 brand-guidelines)
|
|
|
|
主色:
|
|
|
|
| Token | 色值 | 用途 |
|
|
| --- | --- | --- |
|
|
| Dark | `#141413` | 主文本、深色背景 |
|
|
| Light | `#FAF9F5` | 浅色背景、深底上的文本 |
|
|
| Mid Gray | `#B0AEA5` | 次要元素、占位、禁用 |
|
|
| Light Gray | `#E8E6DC` | 细微背景、分隔线、边框 |
|
|
|
|
强调色:
|
|
|
|
| Token | 色值 | 用途 |
|
|
| --- | --- | --- |
|
|
| Orange | `#D97757` | 主强调(按钮、激活、链接 hover) |
|
|
| Blue | `#6A9BCC` | 次强调(信息、链接) |
|
|
| Green | `#788C5D` | 三级强调(成功) |
|
|
|
|
品牌橙近似变体(社区考证):`#C15F3C`(Crail,更深)与 `#CC785C`(Book Cloth),可用作橙色的 hover/active 深档。
|
|
|
|
## 管理面板适配 token
|
|
|
|
官方色板缺管理面板必需的语义色,按同色温补齐:
|
|
|
|
| 语义 | 建议值 | 说明 |
|
|
| --- | --- | --- |
|
|
| 背景主 | `#FAF9F5` | 页面底 |
|
|
| 背景面 | `#F0EEE6` | 侧栏、区块底(Light 与 Light Gray 之间) |
|
|
| 卡片 | `#FFFFFF` | 白卡浮在暖底上,配 1px `#E8E6DC` 边框 |
|
|
| 文本主/次 | `#141413` / `#6B6A63` | 次级文本由 Dark 与 Mid Gray 插值 |
|
|
| 主强调 | `#D97757`,hover `#C15F3C` | |
|
|
| 成功 / 信息 | `#788C5D` / `#6A9BCC` | 官方绿 / 蓝 |
|
|
| 警告 | `#B8860B` 一档的暖赭 | 与暖色调和谐,避免高饱和黄 |
|
|
| 危险 | `#BF4D43` 一档的砖红 | 避免纯红 `#FF0000` 系 |
|
|
| 暗色模式 | 底 `#141413`、面 `#1F1E1D`、边框 `#33322E` | 文本反转用 Light |
|
|
| 圆角 | 按钮/输入 4px,卡片 6px,最大不超过 8px | 拒绝大圆角 |
|
|
| 阴影 | 无或 `0 1px 2px rgba(20,20,19,.05)` | 分层靠边框 |
|
|
|
|
## 字体
|
|
|
|
- 官方品牌字体 **Styrene**(无衬线)+ **Tiempos**(衬线)为商业授权,不可直接使用;官方开源替代:标题 **Poppins**、正文 **Lora**。
|
|
- 本项目:UI 与数据全用无衬线(`Inter` 或 `Poppins`,中文回退 `PingFang SC` / `Noto Sans SC`);OCID、IP、日志等用等宽(`JetBrains Mono` / `ui-monospace`);衬线(Lora)只做登录页/大标题的编辑风点缀,可选。
|
|
|
|
## 反模式清单(设计稿与代码评审逐条对照)
|
|
|
|
大圆角(>8px 的按钮/卡片)、任何渐变(含品牌色渐变文字)、玻璃拟态/毛玻璃、霓虹光晕、彩色大投影、emoji 当图标、深紫深蓝"AI 感"配色、满屏色块卡片、弹跳动效。
|
|
|
|
## 结构复用约定(2026-07-21 样式一致性收敛后固化)
|
|
|
|
- **面板头**:一律用 `PanelHeader`(title + desc + 默认插槽放右侧动作 + `#title-extra` 放徽标/计数),不再手写 `border-b … px-4.5 py-3.5` 头部;结构确实特殊的(带面包屑/筛选工具条)保持自定义但 padding 必须 `px-4.5 py-3.5`。面板一级标题 `text-sm font-semibold`,`text-[13px] font-semibold` 只允许出现在面板内的二级小节/行内标题。
|
|
- **空态/错误态**:整卡(panel 级)一律 `EmptyCard`(错误态 `title="…加载失败" :note="错误信息"`);表格内空态交给 NDataTable 自带 empty;手写灰字仅限行内小空位(如 IPv6「无」)。
|
|
- **浮层投影**:下拉/确认弹层/整页提示卡统一 `shadow-overlay`(@theme `--shadow-overlay`),圆角不超过 `rounded-lg`;禁止再写 `shadow-[0_18px…]` 一类逐处任意值。
|
|
- **恒暗页面**(网页 VNC 等):色值从 `@/theme/tokens` 的 `darkTokens` 取,不硬编码 hex;需要给组件 prop 传具体色时同样从 tokens/darkTokens 引。
|
|
|
|
## 移动端形态约定(2026-07-21 移动端适配 B0-B4 固化)
|
|
|
|
- **断点**:`md`(768px)为移动/桌面切换线,JS 层条件渲染一律 `useIsMobile()`(与 CSS 断点同源);`lg` 仅做网格降列。
|
|
- **弹窗**:表单弹窗一律 `FormModal`(移动端自动全屏 + 页脚常驻);直接用原生 NModal 的(详情查看类)接 `useMobileModal(width)` 取 `:style` 与 `:class`,全屏布局规则 `.form-modal-mobile` 在 `main.css`(全局,teleport 与跨 chunk 都够得到)。组件 scoped 里给弹窗内容限高须包 `@media (min-width: 768px)`——main.css 先于组件样式加载,同特异性覆盖不可靠。
|
|
- **表格**:所有 NDataTable 必配 `scroll-x`(内容合计宽);受控分页 reactive 对象加 `get simple() { return isMobile.value }`(移动端页码槽放不下);高频列表页(租户/实例式)另供 `MobileCardList` 卡片形态,表格 `v-if="!isMobile"`。
|
|
- **筛选器**:多控件筛选一律包 `FilterBar`(桌面 wrap、移动横滚+右缘渐隐);面板头单控件筛选桌面放 `PanelHeader` 插槽、移动独立行(`v-if="isMobile"` 的 `border-b px-4.5 py-2.5` 全宽行)。
|
|
- **详情页 hero**:panel 内「返回链接 → 标题行(page-title+徽标+flex-1+按钮组)→ 移动折叠 toggle → meta grid」四段式;操作按钮组包 `flex items-center gap-2 max-md:w-full`,移动端整组独立成行。
|
|
- **导航**:顶级页面进 `layouts/navItems.ts`(侧栏/底栏/「更多」抽屉唯一来源);新增页面须归入 `mobileTabNames`(高频四项)或 `mobileMoreNames`。
|
|
- **构建**:新增大体积三方库须在 `vite.config.ts` 的 `vendorChunk` 归入命名组(office 系深依赖靠 importer 链上溯),否则聚成匿名 index 块逃逸 PWA 预缓存排除(`globIgnores: office-*`)并可能触发 chunk 告警。
|
|
|
|
## Common Mistake: NModal 宽度不能用 Tailwind class
|
|
|
|
**Symptom**:`<NModal preset="card" class="w-[480px]">` 弹窗实际渲染为全宽。
|
|
|
|
**Cause**:naive-ui 的样式为运行时注入(CSSinJS,`<style>` 追加在 head 尾部),其 `.n-card{width:100%}` 与 Tailwind utilities 同特异性但**后加载**,覆盖 `w-[480px]`。
|
|
|
|
**Fix / Prevention**:弹窗宽度一律用内联 style(优先级最高),与 FormModal.vue 同法:
|
|
|
|
```html
|
|
<!-- Bad:被 naive-ui 运行时样式覆盖 -->
|
|
<NModal preset="card" class="w-[480px] max-w-[92vw]">
|
|
|
|
<!-- Good -->
|
|
<NModal preset="card" :style="{ width: '480px', maxWidth: 'calc(100vw - 24px)' }">
|
|
```
|
|
|
|
## Common Mistake:编辑 SFC 时残留 `defineProps<...>()()` 双括号
|
|
|
|
**Symptom**:运行时 `ReferenceError: defineProps is not defined`(setup 内),但 `vue-tsc`、`vite build` 全绿——编译期完全静默。
|
|
|
|
**Cause**:对 `defineProps<{...}>()` 做字符串替换时旧串漏了尾部 `()`,替换后残留成 `()()`。`@vue/compiler-sfc` 只识别「宏调用语句」,对「调用宏返回值」的表达式不做宏转换,把 `defineProps` 原样保留成运行时标识符。
|
|
|
|
**Prevention**:替换 defineProps/defineEmits 行必须包含**完整语句(含结尾 `()`)**;宏相关改动后,不能只信 build 全绿,须实际打开一次使用该组件的页面(详情弹窗等惰性挂载组件尤其容易漏)。
|
|
|
|
## Common Mistake:scoped style 中 `:global(...)` 后接后代选择器会丢失后代
|
|
|
|
**Symptom**:`:global(html.dark) .totp-qr { ... }` 编译产物变成 `html.dark { ... }`——`.totp-qr` 整段丢失,规则错误落到 `html` 元素上(background/padding 直接作用于全页),目标元素完全不匹配。build 全绿、无告警。
|
|
|
|
**Cause**:`@vue/compiler-sfc` 的 scoped 转换对「`:global()` 后跟普通后代选择器」的组合处理不可靠,后代部分被丢弃而非追加 scope id。
|
|
|
|
**Fix / Prevention**:完整选择器整体放进 `:global()`:
|
|
|
|
```css
|
|
/* Bad:编译后 .totp-qr 丢失,规则落到 html.dark 本身 */
|
|
:global(html.dark) .totp-qr { background: #f5f4ed !important; }
|
|
|
|
/* Good:原样输出 html.dark .totp-qr(自命名 class 需保证全局唯一) */
|
|
:global(html.dark .totp-qr) { background: #f5f4ed !important; }
|
|
```
|
|
|
|
验证方法:build 后 `grep -o 'html\.dark[^}]*}' dist/assets/<view>-*.css` 核对编译产物选择器,或线上 DevTools 看 computed style 是否命中。
|
|
|
|
## Common Mistake:AppInputNumber 全局样式假定 suffix 只有步进两键
|
|
|
|
**Symptom**:`AppInputNumber` 加 `clearable` 后步进箭头挤出输入框右缘(2026-07 抢机表单「引导卷」实例)。
|
|
|
|
**Cause**:`main.css` 的 `.app-input-number` 把 `.n-input__suffix` 设为 26px 宽 2 行网格,并用 `:last-child` / `:nth-last-child(2)` 定位 add / minus;naive-ui 在 `clearable` 时于 suffix 首位额外渲染 `.n-base-clear`(无值时也存在),网格自动放置把它排进隐式第 2 列,竖栏被撑爆。
|
|
|
|
**Fix / Prevention**:已在 `main.css` 全局处理——`.n-base-clear` 绝对定位脱离网格流悬浮竖栏左侧,`.n-input:has(.n-base-clear)` 的 wrapper `padding-right` 加宽避让。今后给 `AppInputNumber` 追加会向 suffix 注入元素的 prop(loading / 自定义 suffix 插槽等)时,须同步检查该网格假设。
|
|
|
|
## 组件速查:NQrCode 去白底
|
|
|
|
NQrCode 自带 inline `background-color:#FFF; padding:12px`(props 可控)。两主题都不垫任何色块的方案:`:padding="0" background-color="transparent"` + `:color="app.dark ? '#f5f4ed' : '#141413'"`——暗色下反色渲染(浅模块透明底),主流扫码器(iOS 相机 / Google Lens / 新版 ML Kit)可识别,扫不动时弹窗内的手动密钥是兜底。color 是响应式 prop,切主题自动重绘。(历史方案「html.dark 下垫 #f5f4ed 浅底」因用户不接受色块框已废弃。)
|