devperf/frontend/src/styles/global.css
zyc 4a2ed8d414 feat(ui+perf): Editorial Data Console 重设计 + 接口性能 + ROI 权限锁
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>
2026-05-22 15:28:48 +08:00

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);
}