This commit is contained in:
董宏凯 committed 2026-05-22 12:34:11 +08:00
1 parent 6e88c5a1c5
commit e076abe759
3 files changed
+433 -70

No files matched your search

+310
View File
@@ -0,0 +1,310 @@
# 浅色红色 UI 规范对齐 — 实施计划
> 依据:`H:\页面设计\UI设计规范\浅色红色设计规范-前端用.pdf`(共 104 页)
> 范围:**仅对齐 Light 模式(红色主题)**,**不改动图表部分**
---
## ⚠️ 重要约束(必读)
### 1. 主题切换必须保留
项目使用 `next-themes` 实现 Light/Dark 切换:
- `:root` → Light 模式(红色品牌,对应本规范)
- `.dark` → Dark 模式(蓝色品牌,**不在本次范围内**)
- 切换组件入口:`src/components/theme-provider.tsx`
**所有改动只能动 `:root` 块和 light-only 选择器,禁止动 `.dark` 块和 `dark:` 前缀样式。**
### 2. 不动图表
本次跳过 `chart-1` ~ `chart-5` 颜色变量和所有图表组件。
### 3. 同一 token 双模式都用时如何改
若变量同时被 light/dark 使用(如 `--radius`、按钮 `h-*`、`--primary-foreground`),改动后需在 Dark 模式下回归检查,确保没有破坏。
---
## 改动总目标对照
| 类别 | UI 规范 | 现状 | 差异 |
|------|--------|------|------|
| 主品牌红 | `#F5222D` | `#d92121` (`oklch(0.52 0.19 27)`) | 偏差 |
| 深色品牌 | `#CF1322` / `#A8071A` | 未使用 | 缺失 |
| 圆角-按钮 | 2px | 12px (`--radius: 0.75rem`) | 严重偏大 |
| 圆角-卡片 | 4px | 12px | 严重偏大 |
| 按钮高度(正常)| 32px | 36px (`h-9`) | 偏大 4px |
| 按钮高度(小)| 24px | 32px (`h-8`) | 偏大 8px |
| 输入框高度 | 32px | 36px (`h-9`) | 偏大 4px |
| 中文字体 | NotoSansSChinese | 系统字体 | 未引入 |
| 数字字体 | D-DIN | 系统字体 | 未引入 |
| 间距体系 | 8px 基准(4/8/12/16/24/32/40/56)| Tailwind 默认 | 基本一致 |
---
## 分步实施计划
### Step 1 — 颜色变量对齐(Light 模式)
**文件**:`src/styles/globals.css`
**只动 `:root` 块(第 242-277 行),保持 `.dark` 块原样**
#### 1.1 主色映射
| 角色 | 规范值 | 用途 |
|------|--------|------|
| 主色 | `#F5222D` | `--primary`、`--ring`、`--sidebar-primary` |
| 主色 hover | `#CF1322` | 按钮 hover 状态(新增辅助变量) |
| 主色 active | `#A8071A` | 按钮按下状态 |
| 主色淡 1 | `#FEEDED` | `--accent`(极淡红,背景) |
| 主色淡 2 | `#FDDADC` | hover bg、tag 背景 |
| 主色淡 3 | `#FCB5B9` | 浅边框、selected 态 |
| 危险色 | `#F5222D` | `--destructive` |
#### 1.2 新增工程变量(建议加在 `:root` 内)
```css
/* 红色主题阶梯色(规范色板)*/
--brand-50: #FEFAFA;
--brand-100: #FEF6F6;
--brand-200: #FEEDED;
--brand-300: #FDDADC;
--brand-400: #FCB5B9;
--brand-500: #FA9196;
--brand-600: #F86C73;
--brand-700: #F74750;
--brand-800: #F5222D; /* ← 主色 */
--brand-900: #CF1322; /* ← hover */
--brand-950: #A8071A; /* ← active */
/* 辅色(规范)*/
--accent-orange: #FF9900;
--accent-green: #2FD69A;
--accent-cyan: #45C3F3;
```
#### 1.3 `--workspace-brand` 同步
`:root` 内:`--workspace-brand: #F5222D;`(从 `#d92121` 改)
`.dark` 内:**不动**(保持 `#3b82f6` 蓝色)
#### 1.4 验收
- 切到 Light 模式:所有按钮、链接、选中态使用规范红
- 切到 Dark 模式:依然是原蓝色品牌(无变化)
---
### Step 2 — 圆角规范化
**文件**:`src/styles/globals.css`(`--radius` 是全局变量,light/dark 共用)
#### 2.1 改动方案(推荐)
按规范:按钮 2px、卡片/页面框 4px。但当前许多组件依赖 `rounded-md` / `rounded-lg`,全局把 `--radius` 改成 4px 会引起视觉跳变。
**采用细粒度策略:**
```css
:root {
--radius: 0.25rem; /* 4px → 卡片/页面框 */
}
@theme inline {
--radius-sm: 2px; /* 按钮、tag */
--radius-md: calc(var(--radius) - 1px); /* 3px → 输入框 */
--radius-lg: var(--radius); /* 4px → 卡片 */
--radius-xl: calc(var(--radius) + 4px); /* 8px → 弹窗 */
--radius-2xl: calc(var(--radius) + 8px); /* 12px → 大卡片 */
}
```
#### 2.2 例外保留(设计感样式)
以下组件**圆角不改**,保持设计稿原貌:
- `.workspace-user-bubble`(聊天气泡,圆角 `1.6rem 1.6rem 0.5rem 1.6rem`)
- `.workspace-assistant-card`(圆角 `1.75rem`)
- `.gemini-gradient-border`(圆角 `1.5rem`)
#### 2.3 验收
- 普通按钮变为近直角(2px)
- 卡片维持温和圆角(4px)
- 聊天气泡、AI 卡片不变形
---
### Step 3 — 按钮组件尺寸对齐
**文件**:`src/components/ui/button.tsx`
#### 3.1 改 size 变体(规范:小 24px / 正常 32px)
| size | 规范 | 当前 | 改后 |
|------|------|------|------|
| `sm` | 24px | h-8 (32px) | `h-6 px-2 text-xs` |
| `default` | 32px | h-9 (36px) | `h-8 px-3 text-sm` |
| `lg` | — | h-10 (40px) | 保留 `h-10`(用于 Banner/登录场景)|
#### 3.2 圆角
- 默认按钮:`rounded-sm`(2px)
#### 3.3 主按钮配色
- bg:`bg-primary`
- hover:`hover:bg-[var(--brand-900)]`(深红 `#CF1322`)
- active:`active:bg-[var(--brand-950)]`(更深 `#A8071A`)
- disabled:`disabled:opacity-50`(保留)
#### 3.4 次要按钮 / 文本按钮
- 次要按钮(`outline` variant):边框 `border-primary`,文字 `text-primary`
- 文本按钮(`ghost` variant):仅文字色 `text-primary`,hover 背景 `--brand-200`
#### 3.5 验收
检查所有调用 `<Button>` 的页面:
- 工具栏按钮(之前 h-9 → 现在 h-8)
- 弹窗确认/取消(之前 h-9 → 现在 h-8)
- Landing 主 CTA 按钮(保留 `lg`)
---
### Step 4 — 输入框尺寸对齐
#### 4.1 `src/components/ui/input.tsx`
- `h-9` → `h-8`(32px)
- `rounded-md`(沿用 3px)
- 边框:`border-input`(变量已是浅灰,不变)
- focus:`focus-visible:ring-[2px] focus-visible:ring-primary/35`
#### 4.2 `src/components/ui/textarea.tsx`
- 最小高度建议 `min-h-16`
- 圆角 `rounded-md`
#### 4.3 `src/components/ui/select.tsx`
- `size: default` → `h-8`
- `size: sm` → `h-6`
#### 4.4 验收
- 搜索框、表单输入、下拉框高度一致(32px)
- 与按钮对齐时高度一致
---
### Step 5 — 字体引入
#### 5.1 引入字体文件
在 `frontend-web/public/fonts/` 下放置:
- `NotoSansSC-Regular.woff2` / `Medium.woff2` / `Bold.woff2`
- `D-DIN-Regular.woff2` / `Bold.woff2`
> 若需在线方式,可用 Google Fonts CDN:
> `https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap`
#### 5.2 `globals.css` 顶部添加 `@font-face`
```css
@font-face {
font-family: 'Noto Sans SC';
src: url('/fonts/NotoSansSC-Regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
/* Medium / Bold 同上 */
@font-face {
font-family: 'D-DIN';
src: url('/fonts/D-DIN-Regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
```
#### 5.3 修改 `--font-sans`(globals.css 第 74 行)
```css
--font-sans:
'Noto Sans SC', ui-sans-serif, system-ui, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-numeric: 'D-DIN', 'Noto Sans SC', sans-serif;
```
#### 5.4 数字字体工具类
新增 `.font-numeric { font-family: var(--font-numeric); font-variant-numeric: tabular-nums; }`
仪表盘、表格中重要数字加 `className="font-numeric"`。
#### 5.5 验收
- 中文文本回退到 Noto Sans SC
- Dark 模式同样生效(字体是全局变量)
---
### Step 6 — Badge / 状态标签色对齐
**文件**:`src/components/ui/badge.tsx`
规范状态色(参考 PDF 第 67 页):
| 状态 | 颜色定位 | 推荐 token |
|------|---------|-----------|
| 未开始 | 中性灰 | `bg-muted text-muted-foreground` |
| 等待中 | 蓝/青 | `bg-blue-50 text-blue-600`(保留) |
| 进行中 | 主红 | `bg-[var(--brand-200)] text-primary` |
| 已完成 | 绿 | `bg-emerald-50 text-emerald-600` |
| 已失败 | 红/橙 | `bg-red-50 text-red-600` |
| 待更新 | 黄/橙 | `bg-amber-50 text-amber-700` |
需要新增 `variant`:`waiting`、`processing`、`success`、`failed`、`pending`。
圆角统一 `rounded-sm`,padding `px-2 py-0.5`,字号 `text-xs`。
---
### Step 7 — 全局间距 & 阴影规范化
#### 7.1 间距
规范基于 8px 网格(4 / 8 / 12 / 16 / 24 / 32 / 40 / 56)。Tailwind 默认 4px 步进(`p-1`=4, `p-2`=8...)已兼容。
**只需 review**:检查页面是否有 `p-2.5`、`gap-1.5` 等非 8 的倍数,统一改成 8 的倍数。
#### 7.2 阴影
规范阴影仅用于"标题+操作"和"卡片新增"两类。其余卡片保持平。
新增工具类(可选):
```css
.shadow-card { box-shadow: 0 2px 8px 0 rgba(0,0,0,0.06); }
.shadow-card-hover { box-shadow: 0 4px 12px 0 rgba(0,0,0,0.08); }
```
---
### Step 8 — 验收清单
每步完成后检查:
- [ ] **Light 模式**视觉对齐规范(参考 PDF 关键页:5、7、10、36、66)
- [ ] **Dark 模式**完全无变化(切换主题验证)
- [ ] 首页 / Workspace / 聊天 / 设置弹窗 → 按钮和输入框高度统一
- [ ] 主题切换按钮点击 → 颜色平滑切换,无错位/闪烁
- [ ] `/landing` 强制 Dark 模式仍正常
- [ ] 控制台无 CSS 变量未定义警告
---
## 执行顺序建议
```
Step 1 (颜色变量) ─┐
├→ Step 3 (按钮) ─┐
Step 2 (圆角) ─┘ ├→ Step 6 (Badge) ─→ Step 7 (间距/阴影) ─→ Step 8 (验收)
│
Step 4 (输入框) ──┤
Step 5 (字体) ────┘
```
并行可行:Step 1 + Step 2 完成后,Step 3/4/5 可同时推进。
---
## 风险提示
1. **圆角从 12px → 4px 是大幅度视觉变化**,建议先在分支上做、配截图比对再合并
2. 字体文件体积较大(NotoSansSC 全字符集 ~10MB),建议子集化或仅引入需要的字重
3. `--radius-sm` 改后可能影响 dropdown menu / popover 等小组件外观,需逐个检查
4. Dark 模式下的红色 `--workspace-brand` 已是蓝色,**不要把蓝色也改成 #F5222D**
+119 -69
View File
@@ -1,12 +1,24 @@
import React, { useState } from 'react';
import { Bell, Search, User, ChevronDown, Moon, Palette } from 'lucide-react';
import { NotificationDialog } from '@/components/workspace/notification-dialog';
import { useTheme } from '../contexts/ThemeContext';
import { useUnreadCount } from '@/core/notifications';
import { Bell, ChevronDown, Moon, Palette, User } from "lucide-react";
import { useState } from "react";
const Header: React.FC = () => {
import { NotificationDialog } from "@/components/workspace/notification-dialog";
import { Button } from "@/components/ui/button";
import { useUnreadCount } from "@/core/notifications";
import { cn } from "@/lib/utils";
import { useTheme } from "../contexts/ThemeContext";
const iconButtonClass = (isDark: boolean) =>
cn(
"size-8 shrink-0 rounded-sm",
isDark
? "text-slate-400 hover:bg-slate-800/50 hover:text-white"
: "text-white hover:bg-[var(--brand-950)] active:bg-[#8a0618]",
);
const Header = () => {
const { theme, toggleTheme } = useTheme();
const isDark = theme === 'dark';
const isDark = theme === "dark";
const [notificationOpen, setNotificationOpen] = useState(false);
const { data: unreadCount = 0 } = useUnreadCount();
@@ -16,72 +28,110 @@ const Header: React.FC = () => {
open={notificationOpen}
onOpenChange={setNotificationOpen}
/>
<header className={`h-16 flex items-center justify-between px-6 theme-transition ${
isDark
? 'bg-slate-900/50 backdrop-blur-xl border-b border-blue-800/30'
: 'bg-red-600 border-b border-red-700'
}`}>
{/* 左侧 - 搜索框 */}
<div className="flex items-center gap-4">
<header
className={cn(
"theme-transition flex h-16 items-center justify-between border-b px-4",
isDark
? "border-blue-800/30 bg-slate-900/50 backdrop-blur-xl"
: "border-[var(--brand-950)] bg-[var(--brand-900)]",
)}
>
<div className="flex min-w-0 flex-1 items-center gap-4" />
</div>
<div className="flex shrink-0 items-center gap-2">
<Button
type="button"
variant="ghost"
size="icon-sm"
onClick={toggleTheme}
className={iconButtonClass(isDark)}
title={isDark ? "切换到红白主题" : "切换到深色主题"}
>
{isDark ? (
<Palette className="size-4" />
) : (
<Moon className="size-4" />
)}
</Button>
{/* 右侧 - 用户信息和通知 */}
<div className="flex items-center gap-4">
{/* 主题切换按钮 */}
<button
onClick={toggleTheme}
className={`p-2 rounded-lg transition-all duration-200 ${
isDark
? 'hover:bg-slate-800/50 text-slate-400 hover:text-white'
: 'hover:bg-red-500 text-white'
}`}
title={isDark ? '切换到红白主题' : '切换到深色主题'}
>
{isDark ? <Palette className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
</button>
<Button
type="button"
variant="ghost"
size="icon-sm"
onClick={() => setNotificationOpen(true)}
className={cn(iconButtonClass(isDark), "relative")}
title="通知"
>
<Bell className="size-4" />
{unreadCount > 0 && (
<span
className={cn(
"absolute top-1 right-1 flex h-4 min-w-4 items-center justify-center rounded-sm px-1 text-[10px] leading-none font-medium",
isDark
? "bg-cyan-500 text-white"
: "bg-white tabular-nums text-[var(--brand-900)]",
)}
>
{unreadCount > 99 ? "99+" : unreadCount}
</span>
)}
</Button>
{/* 通知 */}
<button
type="button"
onClick={() => setNotificationOpen(true)}
className={`relative p-2 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-800/50' : 'hover:bg-red-500'
}`}
title="通知"
>
<Bell className={`w-5 h-5 ${isDark ? 'text-slate-400' : 'text-white'}`} />
{unreadCount > 0 && (
<span
className={`absolute top-1 right-1 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium leading-none text-white ${
isDark ? 'bg-cyan-500' : 'bg-white text-red-600'
}`}
<div
className={cn(
"mx-1 h-8 w-px",
isDark ? "bg-blue-800/30" : "bg-white/30",
)}
aria-hidden
/>
<Button
type="button"
variant="ghost"
className={cn(
"h-8 gap-2 rounded-sm px-2",
isDark
? "text-slate-200 hover:bg-slate-800/50"
: "text-white hover:bg-[var(--brand-950)] active:bg-[#8a0618]",
)}
>
<div
className={cn(
"flex size-8 shrink-0 items-center justify-center rounded-sm",
isDark
? "bg-gradient-to-br from-blue-500 to-cyan-400"
: "bg-white",
)}
>
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</button>
{/* 分隔线 */}
<div className={`w-px h-8 ${isDark ? 'bg-blue-800/30' : 'bg-red-500'}`} />
{/* 用户信息 */}
<button className={`flex items-center gap-3 px-3 py-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-800/50' : 'hover:bg-red-500'
}`}>
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
isDark ? 'bg-gradient-to-br from-blue-500 to-cyan-400' : 'bg-white'
}`}>
<User className={`w-4 h-4 ${isDark ? 'text-white' : 'text-red-600'}`} />
</div>
<div className="text-left">
<p className="text-sm font-medium text-white">张策略</p>
<p className={`text-xs ${isDark ? 'text-blue-400/60' : 'text-red-200'}`}>策略分析师</p>
</div>
<ChevronDown className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-white'}`} />
</button>
</div>
</header>
<User
className={cn(
"size-4",
isDark ? "text-white" : "text-[var(--brand-900)]",
)}
/>
</div>
<div className="hidden text-left sm:block">
<p className="text-sm leading-tight font-medium text-white">
张策略
</p>
<p
className={cn(
"text-xs leading-tight",
isDark ? "text-blue-400/60" : "text-white/75",
)}
>
策略分析师
</p>
</div>
<ChevronDown
className={cn(
"size-4 shrink-0",
isDark ? "text-slate-500" : "text-white/80",
)}
/>
</Button>
</div>
</header>
</>
);
};
+4 -1
View File
@@ -240,8 +240,11 @@
}
:root {
--workspace-brand: #d92121;
--workspace-brand: #f5222d;
--workspace-brand-foreground: #ffffff;
--brand-200: #feeded;
--brand-900: #cf1322;
--brand-950: #a8071a;
--radius: 0.75rem;
--background: oklch(1 0 0);
--foreground: oklch(0.22 0.02 25);