UI 重设计 (Editorial Data Console 风): - 设计令牌系统: OKLCH 色彩 + Newsreader/Geist/JetBrains Mono 字体 + exp easing - 全局表格基线 (.n-data-table 统一 editorial 风 + .table-shell 卡片容器) - DataCard / Naive UI 主题对齐新 token (深墨青主色 + 暖琥珀强调) - RoiDashboard: 3 KPI 卡片同字号 + chip 多色筛选 + section editorial 节奏 - ProjectRoiBoard: hero 卡 highlight + ytd-strip 节奏化 (10/13/15px 三层字号) - ProjectList: 自适应卡片 + 产品线 NSelect 筛选 + 拆出独立"类型"列 + 文本链接操作 - RevenuePieChart 重设计: donut + 中心总额 + 底部水平图例 (替代外部 callout 截断) - 全部页面 width:100% + clamp() 流体 padding,断点驱动 auto-fit 网格 - AppSidebar 项目子菜单按产品线分组 + 可折叠 + localStorage 持久化 接口性能优化 (N+1 → 批量 + Map 索引): - /api/overview: 8.5s → 0.5s (17×) - 消除 3 处循环 SQL 查询 - /api/okr: 11.3s → 0.3s (37×) - getOKRByPeriod 一次性 inArray 批量 - ROI 三处时间窗 (aggregate/timeseries/events) launchedAt 截断对齐 ROI 权限锁: - 全部 ROI 端点统一 admin (roiRoutes 全局 requireRole) - 路由 /roi + /projects/:id/roi meta.roles=['admin'] - 侧边栏 ROI 入口 + 项目详情打标按钮/分类标签全部 v-if isAdmin Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
349 lines
12 KiB
CSS
349 lines
12 KiB
CSS
/* ============================================================
|
|
* DevPerf 2.0 设计令牌系统
|
|
* 审美方向: Editorial Data Console (Linear/Stripe 精致感 + 数据密度)
|
|
* 反 AI 味: 不用 Inter/Roboto、不用紫蓝渐变、不用 glassmorphism
|
|
* 色彩: OKLCH 感知均匀, 中性色带 0.005-0.012 暖色调
|
|
* ============================================================ */
|
|
|
|
@import url('https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400;6..72,500;6..72,600;6..72,700&family=Geist:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
|
|
|
:root {
|
|
/* ─────────── 色彩系统 (OKLCH) ─────────── */
|
|
/* 主色 — 深墨青(克制信任, 不是 Tailwind blue) */
|
|
--color-primary: oklch(0.32 0.05 200);
|
|
--color-primary-hover: oklch(0.27 0.05 200);
|
|
--color-primary-press: oklch(0.23 0.05 200);
|
|
--color-primary-soft: oklch(0.94 0.018 200);
|
|
|
|
/* 强调色 — 暖琥珀(数据高亮 / CTA) */
|
|
--color-accent: oklch(0.72 0.16 65);
|
|
--color-accent-hover: oklch(0.66 0.17 60);
|
|
--color-accent-soft: oklch(0.96 0.04 75);
|
|
|
|
/* 语义色 */
|
|
--color-success: oklch(0.58 0.13 155);
|
|
--color-success-soft: oklch(0.95 0.04 155);
|
|
--color-warning: oklch(0.72 0.16 65);
|
|
--color-warning-soft: oklch(0.96 0.04 75);
|
|
--color-error: oklch(0.55 0.18 25);
|
|
--color-error-soft: oklch(0.96 0.03 25);
|
|
--color-info: oklch(0.52 0.11 240);
|
|
--color-info-soft: oklch(0.95 0.03 240);
|
|
|
|
/* 中性色阶(暖灰, chroma 0.005~0.012, 不是死灰) */
|
|
--color-bg: oklch(0.985 0.003 80); /* 页面背景 */
|
|
--color-bg-card: oklch(1.000 0.000 0); /* 卡片 */
|
|
--color-bg-subtle: oklch(0.965 0.004 80); /* 浅灰区块 */
|
|
--color-bg-hover: oklch(0.955 0.005 80); /* hover */
|
|
--color-bg-sidebar: oklch(0.18 0.012 230); /* 侧边栏深底带蓝绿 */
|
|
--color-bg-sidebar-2: oklch(0.21 0.013 230); /* 侧边栏次级 */
|
|
|
|
--color-border-subtle: oklch(0.935 0.006 80);
|
|
--color-border: oklch(0.88 0.008 80);
|
|
--color-border-strong: oklch(0.78 0.010 80);
|
|
|
|
--color-text-muted: oklch(0.58 0.009 80);
|
|
--color-text-secondary: oklch(0.42 0.011 80);
|
|
--color-text-primary: oklch(0.22 0.012 80);
|
|
--color-text-onDark: oklch(0.92 0.005 80);
|
|
--color-text-onDarkMuted:oklch(0.65 0.010 220);
|
|
|
|
/* 兼容老变量 (保持现有组件不破) */
|
|
--color-primary-hex: #1f3a45;
|
|
--color-accent-hex: #c47918;
|
|
--color-text-secondary-legacy: var(--color-text-secondary);
|
|
|
|
/* ─────────── 图表色板(OKLCH 衍生, 8 色, 中等饱和) ─────────── */
|
|
--chart-1: oklch(0.32 0.05 200); /* 墨青 */
|
|
--chart-2: oklch(0.58 0.13 155); /* 翠绿 */
|
|
--chart-3: oklch(0.72 0.16 65); /* 琥珀 */
|
|
--chart-4: oklch(0.50 0.15 280); /* 紫罗兰 */
|
|
--chart-5: oklch(0.55 0.18 25); /* 朱砂 */
|
|
--chart-6: oklch(0.62 0.13 105); /* 橄榄 */
|
|
--chart-7: oklch(0.50 0.10 220); /* 钢蓝 */
|
|
--chart-8: oklch(0.65 0.12 320); /* 玫瑰 */
|
|
|
|
/* ─────────── 字体 ─────────── */
|
|
/* Display: Newsreader 衬线(editorial 标题) */
|
|
--font-display: 'Newsreader', 'Source Serif 4', 'Songti SC', 'STSong', Georgia, serif;
|
|
/* Sans: Geist(Vercel 现代无衬线, 替代 Inter) + PingFang */
|
|
--font-sans: 'Geist', 'PingFang SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', sans-serif;
|
|
/* Mono: JetBrains Mono(等宽 KPI 数字) */
|
|
--font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', Menlo, Consolas, monospace;
|
|
|
|
/* 兼容老变量 */
|
|
--font-heading: var(--font-sans);
|
|
--font-body: var(--font-sans);
|
|
--font-code: var(--font-mono);
|
|
|
|
/* ─────────── 字号比例尺(ratio 1.25, base 14px) ─────────── */
|
|
--text-xs: 11px; /* caption */
|
|
--text-sm: 12px; /* secondary */
|
|
--text-base: 14px; /* body */
|
|
--text-md: 16px; /* lead */
|
|
--text-lg: 18px; /* small heading */
|
|
--text-xl: 22px; /* section title */
|
|
--text-2xl: 28px; /* page title */
|
|
--text-3xl: 36px; /* KPI medium */
|
|
--text-4xl: 46px; /* KPI hero */
|
|
--text-display: clamp(24px, 1.6vw + 0.6rem, 32px); /* 中文友好的页头字号 */
|
|
|
|
/* ─────────── 行高 ─────────── */
|
|
--leading-tight: 1.2;
|
|
--leading-snug: 1.35;
|
|
--leading-normal: 1.55;
|
|
--leading-relaxed:1.7;
|
|
|
|
/* ─────────── 字重 ─────────── */
|
|
--weight-regular: 400;
|
|
--weight-medium: 500;
|
|
--weight-semibold: 600;
|
|
--weight-bold: 700;
|
|
|
|
/* ─────────── 字间距 ─────────── */
|
|
--tracking-tight: -0.02em;
|
|
--tracking-normal: 0;
|
|
--tracking-wide: 0.04em;
|
|
|
|
/* ─────────── 空间(4px 基础) ─────────── */
|
|
--space-0_5: 2px;
|
|
--space-1: 4px;
|
|
--space-2: 8px;
|
|
--space-3: 12px;
|
|
--space-4: 16px;
|
|
--space-5: 20px;
|
|
--space-6: 24px;
|
|
--space-7: 28px;
|
|
--space-8: 32px;
|
|
--space-10: 40px;
|
|
--space-12: 48px;
|
|
--space-14: 56px;
|
|
--space-16: 64px;
|
|
--space-20: 80px;
|
|
--space-24: 96px;
|
|
|
|
/* ─────────── 圆角 ─────────── */
|
|
--radius-xs: 4px;
|
|
--radius-sm: 6px;
|
|
--radius-md: 8px;
|
|
--radius-lg: 10px;
|
|
--radius-xl: 14px;
|
|
--radius-2xl: 18px;
|
|
--radius-full:9999px;
|
|
|
|
/* 兼容老变量 */
|
|
--radius-btn: var(--radius-md);
|
|
--radius-card: var(--radius-xl);
|
|
--radius-modal: var(--radius-2xl);
|
|
--radius-pill: var(--radius-full);
|
|
|
|
/* ─────────── 阴影(多层柔和叠加) ─────────── */
|
|
--shadow-xs: 0 1px 1px oklch(0.22 0.01 80 / 0.03);
|
|
--shadow-sm: 0 1px 2px oklch(0.22 0.01 80 / 0.05), 0 1px 1px oklch(0.22 0.01 80 / 0.03);
|
|
--shadow-md: 0 2px 4px oklch(0.22 0.01 80 / 0.04), 0 4px 8px oklch(0.22 0.01 80 / 0.04), 0 1px 1px oklch(0.22 0.01 80 / 0.03);
|
|
--shadow-lg: 0 4px 8px oklch(0.22 0.01 80 / 0.04), 0 8px 24px oklch(0.22 0.01 80 / 0.06), 0 1px 1px oklch(0.22 0.01 80 / 0.03);
|
|
--shadow-xl: 0 8px 16px oklch(0.22 0.01 80 / 0.04), 0 16px 48px oklch(0.22 0.01 80 / 0.08), 0 1px 1px oklch(0.22 0.01 80 / 0.03);
|
|
--shadow-focus: 0 0 0 3px oklch(0.32 0.05 200 / 0.18);
|
|
--shadow-focus-error: 0 0 0 3px oklch(0.55 0.18 25 / 0.18);
|
|
|
|
/* ─────────── 缓动 + 时长(exponential easing, 反 bounce) ─────────── */
|
|
--ease-out: cubic-bezier(0.25, 1, 0.5, 1);
|
|
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
|
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
|
--ease-default: var(--ease-out); /* 老变量兼容 */
|
|
--ease-entrance: var(--ease-out-expo);
|
|
|
|
--duration-instant: 100ms;
|
|
--duration-fast: 150ms;
|
|
--duration-base: 200ms;
|
|
--duration-medium: 300ms;
|
|
--duration-slow: 500ms;
|
|
--duration-hover: var(--duration-fast);
|
|
--duration-entrance:var(--duration-slow);
|
|
--duration-collapse:var(--duration-medium);
|
|
|
|
/* ─────────── Z-index ─────────── */
|
|
--z-base: 1;
|
|
--z-dropdown: 100;
|
|
--z-sticky: 200;
|
|
--z-modal: 300;
|
|
--z-toast: 9999;
|
|
|
|
/* ─────────── Layout ─────────── */
|
|
--sidebar-width: 240px;
|
|
--sidebar-collapsed-width: 64px;
|
|
--content-max: 1440px;
|
|
}
|
|
|
|
/* ─────────── Reset ─────────── */
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
html {
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-base);
|
|
line-height: var(--leading-normal);
|
|
color: var(--color-text-primary);
|
|
background-color: var(--color-bg);
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
text-rendering: optimizeLegibility;
|
|
font-feature-settings: 'ss01', 'cv01', 'cv11';
|
|
}
|
|
|
|
body {
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* 标题: 中文用 sans(PingFang)更协调, 衬线只用在英文 eyebrow */
|
|
h1, h2, h3 {
|
|
font-family: var(--font-sans);
|
|
font-weight: var(--weight-semibold);
|
|
line-height: var(--leading-tight);
|
|
letter-spacing: var(--tracking-tight);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
h4, h5, h6 {
|
|
font-family: var(--font-sans);
|
|
font-weight: var(--weight-semibold);
|
|
line-height: var(--leading-snug);
|
|
}
|
|
|
|
code, pre, kbd {
|
|
font-family: var(--font-mono);
|
|
font-feature-settings: 'zero', 'ss01';
|
|
}
|
|
|
|
/* 数字: tabular nums (KPI 用) */
|
|
.tabular-nums {
|
|
font-variant-numeric: tabular-nums;
|
|
font-family: var(--font-mono);
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
/* editorial 副标题 */
|
|
.eyebrow {
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--weight-medium);
|
|
text-transform: uppercase;
|
|
letter-spacing: var(--tracking-wide);
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
a {
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
transition: color var(--duration-fast) var(--ease-out);
|
|
}
|
|
a:hover {
|
|
color: var(--color-primary-hover);
|
|
text-decoration: underline;
|
|
text-decoration-thickness: 1px;
|
|
text-underline-offset: 3px;
|
|
}
|
|
|
|
/* focus-visible(键盘用户) */
|
|
:focus-visible {
|
|
outline: none;
|
|
box-shadow: var(--shadow-focus);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
/* Scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--color-border);
|
|
border-radius: var(--radius-full);
|
|
border: 2px solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--color-border-strong);
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
/* 减少动效偏好 */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
transition-duration: 0.01ms !important;
|
|
}
|
|
}
|
|
|
|
/* utility */
|
|
.surface { background: var(--color-bg-card); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-xl); }
|
|
.hairline { border-bottom: 1px solid var(--color-border-subtle); }
|
|
|
|
/* 表格容器统一 wrapper:白底 + 边框 + 圆角,跟 list-card / DataCard 视觉一致 */
|
|
.table-shell {
|
|
background: var(--color-bg-card);
|
|
border: 1px solid var(--color-border-subtle);
|
|
border-radius: var(--radius-xl);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 全局表格基线 (Naive UI NDataTable 统一样式)
|
|
* Editorial Data Console 风格:浅灰表头 + dashed 行分隔 + hover 高亮
|
|
* ============================================================ */
|
|
.n-data-table .n-data-table-th {
|
|
background: var(--color-bg-subtle) !important;
|
|
color: var(--color-text-secondary) !important;
|
|
font-family: var(--font-sans) !important;
|
|
font-size: 12px !important;
|
|
font-weight: var(--weight-medium) !important;
|
|
letter-spacing: 0.02em !important;
|
|
padding: 10px 16px !important;
|
|
border-bottom: 1px solid var(--color-border) !important;
|
|
border-right: none !important;
|
|
}
|
|
|
|
.n-data-table .n-data-table-td {
|
|
padding: 12px 16px !important;
|
|
border-bottom: 1px solid var(--color-border-subtle) !important;
|
|
border-right: none !important;
|
|
font-size: var(--text-sm);
|
|
color: var(--color-text-primary);
|
|
transition: background var(--duration-fast) var(--ease-out);
|
|
}
|
|
|
|
.n-data-table .n-data-table-tr:hover .n-data-table-td {
|
|
background: var(--color-bg-subtle) !important;
|
|
}
|
|
|
|
.n-data-table .n-data-table-tr:last-child .n-data-table-td {
|
|
border-bottom: none !important;
|
|
}
|
|
|
|
/* 数字单元格自动等宽 (用 .tabular-nums class 或 td 内 .tabular-nums span) */
|
|
.n-data-table .n-data-table-td .tabular-nums {
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* 空状态贴齐主题 */
|
|
.n-data-table .n-data-table-empty {
|
|
padding: var(--space-10) var(--space-4) !important;
|
|
color: var(--color-text-muted);
|
|
}
|