feat(web): 采用 Notion 设计系统 — 暖色调 + 白色侧边栏 + Inter 字体
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

引入 Notion 风格的 DESIGN.md 设计系统文件,并全面重构前端 UI:

- 主色从 Indigo (#4F46E5) 迁移到 Notion Blue (#0075de)
- 页面背景从冷灰 (#F1F5F9) 迁移到暖白 (#f6f5f4)
- 侧边栏从深色 (#0F172A) 迁移到白色,活跃项用蓝色指示
- 文字从 Slate 冷色迁移到暖灰系列 (Warm Gray 500/300)
- 圆角从 8px 缩小到 4px(按钮/输入),8px(卡片)
- 阴影改为多层超轻 Notion 风格(最大 opacity 0.05)
- 字体优先使用 Inter,保留中文回退
- 暗色模式适配暖黑色调 (#191918)
- 更新 27 个前端文件的硬编码颜色值
This commit is contained in:
iven
2026-04-20 13:08:22 +08:00
parent 40b37cc776
commit 8f3d2d58e7
27 changed files with 825 additions and 406 deletions

373
DESIGN.md Normal file
View File

@@ -0,0 +1,373 @@
# Design System: Notion
## 1. Visual Theme & Atmosphere
Notion's website embodies the philosophy of the tool itself: a blank canvas that gets out of your way. The design system is built on warm neutrals rather than cold grays, creating a distinctly approachable minimalism that feels like quality paper rather than sterile glass. The page canvas is pure white (`#ffffff`) but the text isn't pure black -- it's a warm near-black (`rgba(0,0,0,0.95)`) that softens the reading experience imperceptibly. The warm gray scale (`#f6f5f4`, `#31302e`, `#615d59`, `#a39e98`) carries subtle yellow-brown undertones, giving the interface a tactile, almost analog warmth.
The custom NotionInter font (a modified Inter) is the backbone of the system. At display sizes (64px), it uses aggressive negative letter-spacing (-2.125px), creating headlines that feel compressed and precise. The weight range is broader than typical systems: 400 for body, 500 for UI elements, 600 for semi-bold labels, and 700 for display headings. OpenType features `"lnum"` (lining numerals) and `"locl"` (localized forms) are enabled on larger text, adding typographic sophistication that rewards close reading.
What makes Notion's visual language distinctive is its border philosophy. Rather than heavy borders or shadows, Notion uses ultra-thin `1px solid rgba(0,0,0,0.1)` borders -- borders that exist as whispers, barely perceptible division lines that create structure without weight. The shadow system is equally restrained: multi-layer stacks with cumulative opacity never exceeding 0.05, creating depth that's felt rather than seen.
**Key Characteristics:**
- NotionInter (modified Inter) with negative letter-spacing at display sizes (-2.125px at 64px)
- Warm neutral palette: grays carry yellow-brown undertones (`#f6f5f4` warm white, `#31302e` warm dark)
- Near-black text via `rgba(0,0,0,0.95)` -- not pure black, creating micro-warmth
- Ultra-thin borders: `1px solid rgba(0,0,0,0.1)` throughout -- whisper-weight division
- Multi-layer shadow stacks with sub-0.05 opacity for barely-there depth
- Notion Blue (`#0075de`) as the singular accent color for CTAs and interactive elements
- Pill badges (9999px radius) with tinted blue backgrounds for status indicators
- 8px base spacing unit with an organic, non-rigid scale
## 2. Color Palette & Roles
### Primary
- **Notion Black** (`rgba(0,0,0,0.95)` / `#000000f2`): Primary text, headings, body copy. The 95% opacity softens pure black without sacrificing readability.
- **Pure White** (`#ffffff`): Page background, card surfaces, button text on blue.
- **Notion Blue** (`#0075de`): Primary CTA, link color, interactive accent -- the only saturated color in the core UI chrome.
### Brand Secondary
- **Deep Navy** (`#213183`): Secondary brand color, used sparingly for emphasis and dark feature sections.
- **Active Blue** (`#005bab`): Button active/pressed state -- darker variant of Notion Blue.
### Warm Neutral Scale
- **Warm White** (`#f6f5f4`): Background surface tint, section alternation, subtle card fill. The yellow undertone is key.
- **Warm Dark** (`#31302e`): Dark surface background, dark section text. Warmer than standard grays.
- **Warm Gray 500** (`#615d59`): Secondary text, descriptions, muted labels.
- **Warm Gray 300** (`#a39e98`): Placeholder text, disabled states, caption text.
### Semantic Accent Colors
- **Teal** (`#2a9d99`): Success states, positive indicators.
- **Green** (`#1aae39`): Confirmation, completion badges.
- **Orange** (`#dd5b00`): Warning states, attention indicators.
- **Pink** (`#ff64c8`): Decorative accent, feature highlights.
- **Purple** (`#391c57`): Premium features, deep accents.
- **Brown** (`#523410`): Earthy accent, warm feature sections.
### Interactive
- **Link Blue** (`#0075de`): Primary link color with underline-on-hover.
- **Link Light Blue** (`#62aef0`): Lighter link variant for dark backgrounds.
- **Focus Blue** (`#097fe8`): Focus ring on interactive elements.
- **Badge Blue Bg** (`#f2f9ff`): Pill badge background, tinted blue surface.
- **Badge Blue Text** (`#097fe8`): Pill badge text, darker blue for readability.
### Shadows & Depth
- **Card Shadow** (`rgba(0,0,0,0.04) 0px 4px 18px, rgba(0,0,0,0.027) 0px 2.025px 7.84688px, rgba(0,0,0,0.02) 0px 0.8px 2.925px, rgba(0,0,0,0.01) 0px 0.175px 1.04062px`): Multi-layer card elevation.
- **Deep Shadow** (`rgba(0,0,0,0.01) 0px 1px 3px, rgba(0,0,0,0.02) 0px 3px 7px, rgba(0,0,0,0.02) 0px 7px 15px, rgba(0,0,0,0.04) 0px 14px 28px, rgba(0,0,0,0.05) 0px 23px 52px`): Five-layer deep elevation for modals and featured content.
- **Whisper Border** (`1px solid rgba(0,0,0,0.1)`): Standard division border -- cards, dividers, sections.
## 3. Typography Rules
### Font Family
- **Primary**: `NotionInter`, with fallbacks: `Inter, -apple-system, system-ui, Segoe UI, Helvetica, Apple Color Emoji, Arial, Segoe UI Emoji, Segoe UI Symbol`
- **OpenType Features**: `"lnum"` (lining numerals) and `"locl"` (localized forms) enabled on display and heading text.
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Display Hero | NotionInter | 64px (4.00rem) | 700 | 1.00 (tight) | -2.125px | Maximum compression, billboard headlines |
| Display Secondary | NotionInter | 54px (3.38rem) | 700 | 1.04 (tight) | -1.875px | Secondary hero, feature headlines |
| Section Heading | NotionInter | 48px (3.00rem) | 700 | 1.00 (tight) | -1.5px | Feature section titles, with `"lnum"` |
| Sub-heading Large | NotionInter | 40px (2.50rem) | 700 | 1.50 | normal | Card headings, feature sub-sections |
| Sub-heading | NotionInter | 26px (1.63rem) | 700 | 1.23 (tight) | -0.625px | Section sub-titles, content headers |
| Card Title | NotionInter | 22px (1.38rem) | 700 | 1.27 (tight) | -0.25px | Feature cards, list titles |
| Body Large | NotionInter | 20px (1.25rem) | 600 | 1.40 | -0.125px | Introductions, feature descriptions |
| Body | NotionInter | 16px (1.00rem) | 400 | 1.50 | normal | Standard reading text |
| Body Medium | NotionInter | 16px (1.00rem) | 500 | 1.50 | normal | Navigation, emphasized UI text |
| Body Semibold | NotionInter | 16px (1.00rem) | 600 | 1.50 | normal | Strong labels, active states |
| Body Bold | NotionInter | 16px (1.00rem) | 700 | 1.50 | normal | Headlines at body size |
| Nav / Button | NotionInter | 15px (0.94rem) | 600 | 1.33 | normal | Navigation links, button text |
| Caption | NotionInter | 14px (0.88rem) | 500 | 1.43 | normal | Metadata, secondary labels |
| Caption Light | NotionInter | 14px (0.88rem) | 400 | 1.43 | normal | Body captions, descriptions |
| Badge | NotionInter | 12px (0.75rem) | 600 | 1.33 | 0.125px | Pill badges, tags, status labels |
| Micro Label | NotionInter | 12px (0.75rem) | 400 | 1.33 | 0.125px | Small metadata, timestamps |
### Principles
- **Compression at scale**: NotionInter at display sizes uses -2.125px letter-spacing at 64px, progressively relaxing to -0.625px at 26px and normal at 16px. The compression creates density at headlines while maintaining readability at body sizes.
- **Four-weight system**: 400 (body/reading), 500 (UI/interactive), 600 (emphasis/navigation), 700 (headings/display). The broader weight range compared to most systems allows nuanced hierarchy.
- **Warm scaling**: Line height tightens as size increases -- 1.50 at body (16px), 1.23-1.27 at sub-headings, 1.00-1.04 at display. This creates denser, more impactful headlines.
- **Badge micro-tracking**: The 12px badge text uses positive letter-spacing (0.125px) -- the only positive tracking in the system, creating wider, more legible small text.
## 4. Component Stylings
### Buttons
**Primary Blue**
- Background: `#0075de` (Notion Blue)
- Text: `#ffffff`
- Padding: 8px 16px
- Radius: 4px (subtle)
- Border: `1px solid transparent`
- Hover: background darkens to `#005bab`
- Active: scale(0.9) transform
- Focus: `2px solid` focus outline, `var(--shadow-level-200)` shadow
- Use: Primary CTA ("Get Notion free", "Try it")
**Secondary / Tertiary**
- Background: `rgba(0,0,0,0.05)` (translucent warm gray)
- Text: `#000000` (near-black)
- Padding: 8px 16px
- Radius: 4px
- Hover: text color shifts, scale(1.05)
- Active: scale(0.9) transform
- Use: Secondary actions, form submissions
**Ghost / Link Button**
- Background: transparent
- Text: `rgba(0,0,0,0.95)`
- Decoration: underline on hover
- Use: Tertiary actions, inline links
**Pill Badge Button**
- Background: `#f2f9ff` (tinted blue)
- Text: `#097fe8`
- Padding: 4px 8px
- Radius: 9999px (full pill)
- Font: 12px weight 600
- Use: Status badges, feature labels, "New" tags
### Cards & Containers
- Background: `#ffffff`
- Border: `1px solid rgba(0,0,0,0.1)` (whisper border)
- Radius: 12px (standard cards), 16px (featured/hero cards)
- Shadow: `rgba(0,0,0,0.04) 0px 4px 18px, rgba(0,0,0,0.027) 0px 2.025px 7.84688px, rgba(0,0,0,0.02) 0px 0.8px 2.925px, rgba(0,0,0,0.01) 0px 0.175px 1.04062px`
- Hover: subtle shadow intensification
- Image cards: 12px top radius, image fills top half
### Inputs & Forms
- Background: `#ffffff`
- Text: `rgba(0,0,0,0.9)`
- Border: `1px solid #dddddd`
- Padding: 6px
- Radius: 4px
- Focus: blue outline ring
- Placeholder: warm gray `#a39e98`
### Navigation
- Clean horizontal nav on white, not sticky
- Brand logo left-aligned (33x34px icon + wordmark)
- Links: NotionInter 15px weight 500-600, near-black text
- Hover: color shift to `var(--color-link-primary-text-hover)`
- CTA: blue pill button ("Get Notion free") right-aligned
- Mobile: hamburger menu collapse
- Product dropdowns with multi-level categorized menus
### Image Treatment
- Product screenshots with `1px solid rgba(0,0,0,0.1)` border
- Top-rounded images: `12px 12px 0px 0px` radius
- Dashboard/workspace preview screenshots dominate feature sections
- Warm gradient backgrounds behind hero illustrations (decorative character illustrations)
### Distinctive Components
**Feature Cards with Illustrations**
- Large illustrative headers (The Great Wave, product UI screenshots)
- 12px radius card with whisper border
- Title at 22px weight 700, description at 16px weight 400
- Warm white (`#f6f5f4`) background variant for alternating sections
**Trust Bar / Logo Grid**
- Company logos (trusted teams section) in their brand colors
- Horizontal scroll or grid layout with team counts
- Metric display: large number + description pattern
**Metric Cards**
- Large number display (e.g., "$4,200 ROI")
- NotionInter 40px+ weight 700 for the metric
- Description below in warm gray body text
- Whisper-bordered card container
## 5. Layout Principles
### Spacing System
- Base unit: 8px
- Scale: 2px, 3px, 4px, 5px, 6px, 7px, 8px, 11px, 12px, 14px, 16px, 24px, 32px
- Non-rigid organic scale with fractional values (5.6px, 6.4px) for micro-adjustments
### Grid & Container
- Max content width: approximately 1200px
- Hero: centered single-column with generous top padding (80-120px)
- Feature sections: 2-3 column grids for cards
- Full-width warm white (`#f6f5f4`) section backgrounds for alternation
- Code/dashboard screenshots as contained with whisper border
### Whitespace Philosophy
- **Generous vertical rhythm**: 64-120px between major sections. Notion lets content breathe with vast vertical padding.
- **Warm alternation**: White sections alternate with warm white (`#f6f5f4`) sections, creating gentle visual rhythm without harsh color breaks.
- **Content-first density**: Body text blocks are compact (line-height 1.50) but surrounded by ample margin, creating islands of readable content in a sea of white space.
### Border Radius Scale
- Micro (4px): Buttons, inputs, functional interactive elements
- Subtle (5px): Links, list items, menu items
- Standard (8px): Small cards, containers, inline elements
- Comfortable (12px): Standard cards, feature containers, image tops
- Large (16px): Hero cards, featured content, promotional blocks
- Full Pill (9999px): Badges, pills, status indicators
- Circle (100%): Tab indicators, avatars
## 6. Depth & Elevation
| Level | Treatment | Use |
|-------|-----------|-----|
| Flat (Level 0) | No shadow, no border | Page background, text blocks |
| Whisper (Level 1) | `1px solid rgba(0,0,0,0.1)` | Standard borders, card outlines, dividers |
| Soft Card (Level 2) | 4-layer shadow stack (max opacity 0.04) | Content cards, feature blocks |
| Deep Card (Level 3) | 5-layer shadow stack (max opacity 0.05, 52px blur) | Modals, featured panels, hero elements |
| Focus (Accessibility) | `2px solid var(--focus-color)` outline | Keyboard focus on all interactive elements |
**Shadow Philosophy**: Notion's shadow system uses multiple layers with extremely low individual opacity (0.01 to 0.05) that accumulate into soft, natural-looking elevation. The 4-layer card shadow spans from 1.04px to 18px blur, creating a gradient of depth rather than a single hard shadow. The 5-layer deep shadow extends to 52px blur at 0.05 opacity, producing ambient occlusion that feels like natural light rather than computer-generated depth. This layered approach makes elements feel embedded in the page rather than floating above it.
### Decorative Depth
- Hero section: decorative character illustrations (playful, hand-drawn style)
- Section alternation: white to warm white (`#f6f5f4`) background shifts
- No hard section borders -- separation comes from background color changes and spacing
## 7. Responsive Behavior
### Breakpoints
| Name | Width | Key Changes |
|------|-------|-------------|
| Mobile Small | <400px | Tight single column, minimal padding |
| Mobile | 400-600px | Standard mobile, stacked layout |
| Tablet Small | 600-768px | 2-column grids begin |
| Tablet | 768-1080px | Full card grids, expanded padding |
| Desktop Small | 1080-1200px | Standard desktop layout |
| Desktop | 1200-1440px | Full layout, maximum content width |
| Large Desktop | >1440px | Centered, generous margins |
### Touch Targets
- Buttons use comfortable padding (8px-16px vertical)
- Navigation links at 15px with adequate spacing
- Pill badges have 8px horizontal padding for tap targets
- Mobile menu toggle uses standard hamburger button
### Collapsing Strategy
- Hero: 64px display -> scales to 40px -> 26px on mobile, maintains proportional letter-spacing
- Navigation: horizontal links + blue CTA -> hamburger menu
- Feature cards: 3-column -> 2-column -> single column stacked
- Product screenshots: maintain aspect ratio with responsive images
- Trust bar logos: grid -> horizontal scroll on mobile
- Footer: multi-column -> stacked single column
- Section spacing: 80px+ -> 48px on mobile
### Image Behavior
- Workspace screenshots maintain whisper border at all sizes
- Hero illustrations scale proportionally
- Product screenshots use responsive images with consistent border radius
- Full-width warm white sections maintain edge-to-edge treatment
## 8. Accessibility & States
### Focus System
- All interactive elements receive visible focus indicators
- Focus outline: `2px solid` with focus color + shadow level 200
- Tab navigation supported throughout all interactive components
- High contrast text: near-black on white exceeds WCAG AAA (>14:1 ratio)
### Interactive States
- **Default**: Standard appearance with whisper borders
- **Hover**: Color shift on text, scale(1.05) on buttons, underline on links
- **Active/Pressed**: scale(0.9) transform, darker background variant
- **Focus**: Blue outline ring with shadow reinforcement
- **Disabled**: Warm gray (`#a39e98`) text, reduced opacity
### Color Contrast
- Primary text (rgba(0,0,0,0.95)) on white: ~18:1 ratio
- Secondary text (#615d59) on white: ~5.5:1 ratio (WCAG AA)
- Blue CTA (#0075de) on white: ~4.6:1 ratio (WCAG AA for large text)
- Badge text (#097fe8) on badge bg (#f2f9ff): ~4.5:1 ratio (WCAG AA for large text)
## 9. Agent Prompt Guide
### Quick Color Reference
- Primary CTA: Notion Blue (`#0075de`)
- Background: Pure White (`#ffffff`)
- Alt Background: Warm White (`#f6f5f4`)
- Heading text: Near-Black (`rgba(0,0,0,0.95)`)
- Body text: Near-Black (`rgba(0,0,0,0.95)`)
- Secondary text: Warm Gray 500 (`#615d59`)
- Muted text: Warm Gray 300 (`#a39e98`)
- Border: `1px solid rgba(0,0,0,0.1)`
- Link: Notion Blue (`#0075de`)
- Focus ring: Focus Blue (`#097fe8`)
### Example Component Prompts
- "Create a hero section on white background. Headline at 64px NotionInter weight 700, line-height 1.00, letter-spacing -2.125px, color rgba(0,0,0,0.95). Subtitle at 20px weight 600, line-height 1.40, color #615d59. Blue CTA button (#0075de, 4px radius, 8px 16px padding, white text) and ghost button (transparent bg, near-black text, underline on hover)."
- "Design a card: white background, 1px solid rgba(0,0,0,0.1) border, 12px radius. Use shadow stack: rgba(0,0,0,0.04) 0px 4px 18px, rgba(0,0,0,0.027) 0px 2.025px 7.85px, rgba(0,0,0,0.02) 0px 0.8px 2.93px, rgba(0,0,0,0.01) 0px 0.175px 1.04px. Title at 22px NotionInter weight 700, letter-spacing -0.25px. Body at 16px weight 400, color #615d59."
- "Build a pill badge: #f2f9ff background, #097fe8 text, 9999px radius, 4px 8px padding, 12px NotionInter weight 600, letter-spacing 0.125px."
- "Create navigation: white header. NotionInter 15px weight 600 for links, near-black text. Blue pill CTA 'Get Notion free' right-aligned (#0075de bg, white text, 4px radius)."
- "Design an alternating section layout: white sections alternate with warm white (#f6f5f4) sections. Each section has 64-80px vertical padding, max-width 1200px centered. Section heading at 48px weight 700, line-height 1.00, letter-spacing -1.5px."
### Iteration Guide
1. Always use warm neutrals -- Notion's grays have yellow-brown undertones (#f6f5f4, #31302e, #615d59, #a39e98), never blue-gray
2. Letter-spacing scales with font size: -2.125px at 64px, -1.875px at 54px, -0.625px at 26px, normal at 16px
3. Four weights: 400 (read), 500 (interact), 600 (emphasize), 700 (announce)
4. Borders are whispers: 1px solid rgba(0,0,0,0.1) -- never heavier
5. Shadows use 4-5 layers with individual opacity never exceeding 0.05
6. The warm white (#f6f5f4) section background is essential for visual rhythm
7. Pill badges (9999px) for status/tags, 4px radius for buttons and inputs
8. Notion Blue (#0075de) is the only saturated color in core UI -- use it sparingly for CTAs and links
## 10. ERP-Specific Adaptations
> This section adapts the Notion design system for our universal business ERP platform.
> We use Ant Design 5 components but override their theme tokens to match Notion's warm minimalism.
### Color Token Mapping (Ant Design Theme)
- `colorPrimary`: `#0075de` (Notion Blue)
- `colorBgContainer`: `#ffffff`
- `colorBgLayout`: `#f6f5f4` (Warm White)
- `colorBorder`: `rgba(0,0,0,0.1)`
- `colorBorderSecondary`: `rgba(0,0,0,0.06)`
- `colorText`: `rgba(0,0,0,0.95)`
- `colorTextSecondary`: `#615d59`
- `colorTextTertiary`: `#a39e98`
- `colorTextQuaternary`: `#d4d0cc`
- `colorSuccess`: `#1aae39`
- `colorWarning`: `#dd5b00`
- `colorError`: `#e5534b`
- `colorInfo`: `#0075de`
- `borderRadius`: 4
- `borderRadiusLG`: 8
- `borderRadiusSM`: 2
- `fontFamily`: `'Inter', -apple-system, system-ui, 'Segoe UI', Helvetica, Arial, sans-serif`
### Sidebar Navigation Adaptation
- Sidebar background: `#ffffff` (white, not dark)
- Sidebar item text: `rgba(0,0,0,0.95)`
- Sidebar item hover: `#f6f5f4` background
- Sidebar item active: `#f2f9ff` background + `#0075de` left indicator
- Sidebar group headers: `#615d59` uppercase 11px weight 600 letter-spacing 0.5px
- Sidebar collapsed: icon-only with tooltip
### Table/List Adaptation
- Table header: `#f6f5f4` background, `#615d59` text, 13px weight 600
- Table row hover: `#fafaf9` (slightly warmer than pure white)
- Table row selected: `#f2f9ff` background
- Table border: `1px solid rgba(0,0,0,0.06)`
- Striped rows: optional, `#fafaf9` on even rows
### Form Adaptation
- Input border: `1px solid rgba(0,0,0,0.15)`
- Input focus: `#0075de` ring + soft shadow
- Input background: `#ffffff`
- Label: `rgba(0,0,0,0.95)` 14px weight 500
- Help text: `#a39e98` 12px weight 400
- Error state: `#e5534b` border + text
### Dashboard Widget Adaptation
- Stat cards: white bg, whisper border, 12px radius, multi-layer shadow
- Metric number: Inter 32px weight 700
- Metric label: `#615d59` 13px weight 500
- Action list items: `#f6f5f4` bg on hover, whisper border between items
- Status badges: pill (9999px) with semantic color tints
### Dark Mode Adaptation
- Background: `#191918` (warm dark, slightly lighter than pure black)
- Surface: `#232322` (cards, modals)
- Text primary: `rgba(255,255,255,0.95)`
- Text secondary: `#a39e98`
- Border: `rgba(255,255,255,0.08)`
- Blue accent: `#62aef0` (lighter for dark backgrounds)
- Sidebar: `#1e1e1d`

View File

@@ -31,52 +31,52 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
const themeConfig = {
token: {
colorPrimary: '#4F46E5',
colorSuccess: '#059669',
colorWarning: '#D97706',
colorError: '#DC2626',
colorInfo: '#2563EB',
colorBgLayout: '#F1F5F9',
colorBgContainer: '#FFFFFF',
colorBgElevated: '#FFFFFF',
colorBorder: '#E2E8F0',
colorBorderSecondary: '#F1F5F9',
borderRadius: 8,
borderRadiusLG: 12,
borderRadiusSM: 6,
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Helvetica Neue', Helvetica, Arial, sans-serif",
colorPrimary: '#0075de',
colorSuccess: '#1aae39',
colorWarning: '#dd5b00',
colorError: '#e5534b',
colorInfo: '#0075de',
colorBgLayout: '#f6f5f4',
colorBgContainer: '#ffffff',
colorBgElevated: '#ffffff',
colorBorder: 'rgba(0, 0, 0, 0.1)',
colorBorderSecondary: 'rgba(0, 0, 0, 0.06)',
borderRadius: 4,
borderRadiusLG: 8,
borderRadiusSM: 2,
fontFamily: "'Inter', -apple-system, system-ui, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', Helvetica, Arial, sans-serif",
fontSize: 14,
fontSizeHeading4: 20,
controlHeight: 36,
controlHeightLG: 40,
controlHeightSM: 28,
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.06), 0 1px 2px -1px rgba(0, 0, 0, 0.06)',
boxShadowSecondary: '0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.07)',
boxShadow: 'none',
boxShadowSecondary: 'rgba(0,0,0,0.04) 0px 4px 18px, rgba(0,0,0,0.027) 0px 2.025px 7.85px',
},
components: {
Button: {
primaryShadow: '0 1px 2px 0 rgba(79, 70, 229, 0.3)',
primaryShadow: 'none',
fontWeight: 500,
},
Card: {
paddingLG: 20,
},
Table: {
headerBg: '#F8FAFC',
headerColor: '#475569',
rowHoverBg: '#F5F3FF',
headerBg: '#fafaf9',
headerColor: '#615d59',
rowHoverBg: '#f2f9ff',
fontSize: 14,
},
Menu: {
itemBorderRadius: 8,
itemBorderRadius: 4,
itemMarginInline: 8,
itemHeight: 40,
itemHeight: 36,
},
Modal: {
borderRadiusLG: 16,
borderRadiusLG: 12,
},
Tag: {
borderRadiusSM: 6,
borderRadiusSM: 4,
},
},
};
@@ -85,20 +85,20 @@ const darkThemeConfig = {
...themeConfig,
token: {
...themeConfig.token,
colorBgLayout: '#0B0F1A',
colorBgContainer: '#111827',
colorBgElevated: '#1E293B',
colorBorder: '#1E293B',
colorBorderSecondary: '#1E293B',
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.3)',
boxShadowSecondary: '0 4px 6px -1px rgba(0, 0, 0, 0.4)',
colorBgLayout: '#191918',
colorBgContainer: '#232322',
colorBgElevated: '#2a2a29',
colorBorder: 'rgba(255, 255, 255, 0.08)',
colorBorderSecondary: 'rgba(255, 255, 255, 0.05)',
boxShadow: 'none',
boxShadowSecondary: 'rgba(0,0,0,0.2) 0px 4px 18px, rgba(0,0,0,0.15) 0px 2px 8px',
},
components: {
...themeConfig.components,
Table: {
headerBg: '#1E293B',
headerColor: '#94A3B8',
rowHoverBg: '#1E293B',
headerBg: '#2a2a29',
headerColor: '#a39e98',
rowHoverBg: '#2a2a29',
},
},
};

View File

@@ -50,7 +50,7 @@ export default function NotificationPanel() {
<Button
type="text"
size="small"
style={{ fontSize: 12, color: '#4F46E5' }}
style={{ fontSize: 12, color: '#0075de' }}
onClick={() => navigate('/messages')}
>
@@ -76,7 +76,7 @@ export default function NotificationPanel() {
cursor: 'pointer',
transition: 'background 0.15s ease',
border: 'none',
background: !item.is_read ? (isDark ? '#1E293B' : '#F5F3FF') : 'transparent',
background: !item.is_read ? (isDark ? '#1e1e1d' : '#f2f9ff') : 'transparent',
}}
onClick={() => {
if (!item.is_read) {
@@ -85,7 +85,7 @@ export default function NotificationPanel() {
}}
onMouseEnter={(e) => {
if (item.is_read) {
e.currentTarget.style.background = isDark ? '#1E293B' : '#F8FAFC';
e.currentTarget.style.background = isDark ? '#1e1e1d' : '#fafaf9';
}
}}
onMouseLeave={(e) => {
@@ -109,7 +109,7 @@ export default function NotificationPanel() {
width: 6,
height: 6,
borderRadius: '50%',
background: '#4F46E5',
background: '#0075de',
flexShrink: 0,
}} />
)}
@@ -132,12 +132,12 @@ export default function NotificationPanel() {
textAlign: 'center',
paddingTop: 8,
marginTop: 4,
borderTop: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
borderTop: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
}}>
<Button
type="text"
onClick={() => navigate('/messages')}
style={{ fontSize: 13, color: '#4F46E5', fontWeight: 500 }}
style={{ fontSize: 13, color: '#0075de', fontWeight: 500 }}
>
</Button>
@@ -166,7 +166,7 @@ export default function NotificationPanel() {
position: 'relative',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = isDark ? '#1E293B' : '#F1F5F9';
e.currentTarget.style.background = isDark ? '#1e1e1d' : '#f6f5f4';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
@@ -175,7 +175,7 @@ export default function NotificationPanel() {
<Badge count={unreadCount} size="small" offset={[4, -4]}>
<BellOutlined style={{
fontSize: 16,
color: isDark ? '#94A3B8' : '#64748B',
color: isDark ? '#a39e98' : '#615d59',
}} />
</Badge>
</div>

View File

@@ -2,65 +2,65 @@
/* ====================================================================
* ERP Platform — Design System Tokens & Global Styles
* Inspired by Linear, Feishu, SAP Fiori modern design language
* Inspired by Notion: warm minimalism, whisper borders, soft elevation
* ==================================================================== */
/* --- Design Tokens (CSS Custom Properties) --- */
:root {
/* Primary Palette */
--erp-primary: #4F46E5;
--erp-primary-hover: #4338CA;
--erp-primary-active: #3730A3;
--erp-primary-light: #EEF2FF;
--erp-primary-light-hover: #E0E7FF;
--erp-primary-bg-subtle: #F5F3FF;
/* Primary Palette — Notion Blue */
--erp-primary: #0075de;
--erp-primary-hover: #005bab;
--erp-primary-active: #004a8c;
--erp-primary-light: #f2f9ff;
--erp-primary-light-hover: #e4f1ff;
--erp-primary-bg-subtle: #f2f9ff;
/* Semantic Colors */
--erp-success: #059669;
--erp-success-bg: #ECFDF5;
--erp-warning: #D97706;
--erp-warning-bg: #FFFBEB;
--erp-error: #DC2626;
--erp-error-bg: #FEF2F2;
--erp-info: #2563EB;
--erp-info-bg: #EFF6FF;
/* Semantic Colors — Notion warm tones */
--erp-success: #1aae39;
--erp-success-bg: #ecfdf5;
--erp-warning: #dd5b00;
--erp-warning-bg: #fff7ed;
--erp-error: #e5534b;
--erp-error-bg: #fef2f2;
--erp-info: #0075de;
--erp-info-bg: #f2f9ff;
/* Neutral Palette */
--erp-bg-page: #F1F5F9;
--erp-bg-container: #FFFFFF;
--erp-bg-elevated: #FFFFFF;
--erp-bg-spotlight: #F8FAFC;
--erp-bg-sidebar: #0F172A;
--erp-bg-sidebar-hover: #1E293B;
--erp-bg-sidebar-active: rgba(79, 70, 229, 0.15);
/* Neutral Palette — Warm neutrals with yellow-brown undertones */
--erp-bg-page: #f6f5f4;
--erp-bg-container: #ffffff;
--erp-bg-elevated: #ffffff;
--erp-bg-spotlight: #fafaf9;
--erp-bg-sidebar: #ffffff;
--erp-bg-sidebar-hover: #f6f5f4;
--erp-bg-sidebar-active: #f2f9ff;
/* Text Colors */
--erp-text-primary: #0F172A;
--erp-text-secondary: #475569;
--erp-text-tertiary: #94A3B8;
--erp-text-inverse: #F8FAFC;
--erp-text-sidebar: #CBD5E1;
--erp-text-sidebar-active: #FFFFFF;
/* Text Colors — Warm near-black */
--erp-text-primary: rgba(0, 0, 0, 0.95);
--erp-text-secondary: #615d59;
--erp-text-tertiary: #a39e98;
--erp-text-inverse: #ffffff;
--erp-text-sidebar: #615d59;
--erp-text-sidebar-active: #0075de;
/* Border Colors */
--erp-border: #E2E8F0;
--erp-border-light: #F1F5F9;
--erp-border-dark: #334155;
/* Border Colors — Whisper borders */
--erp-border: rgba(0, 0, 0, 0.1);
--erp-border-light: rgba(0, 0, 0, 0.06);
--erp-border-dark: rgba(0, 0, 0, 0.15);
/* Shadows */
--erp-shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
--erp-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.06), 0 1px 2px -1px rgba(0, 0, 0, 0.06);
--erp-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.07);
--erp-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.08);
--erp-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.08);
/* Shadows — Multi-layer, ultra-subtle */
--erp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.01), 0 0.175px 1.04px rgba(0, 0, 0, 0.01);
--erp-shadow-sm: rgba(0, 0, 0, 0.04) 0px 4px 18px, rgba(0, 0, 0, 0.027) 0px 2.025px 7.85px, rgba(0, 0, 0, 0.02) 0px 0.8px 2.93px, rgba(0, 0, 0, 0.01) 0px 0.175px 1.04px;
--erp-shadow-md: rgba(0, 0, 0, 0.01) 0px 1px 3px, rgba(0, 0, 0, 0.02) 0px 3px 7px, rgba(0, 0, 0, 0.02) 0px 7px 15px, rgba(0, 0, 0, 0.04) 0px 14px 28px, rgba(0, 0, 0, 0.05) 0px 23px 52px;
--erp-shadow-lg: rgba(0, 0, 0, 0.01) 0px 2px 4px, rgba(0, 0, 0, 0.02) 0px 5px 12px, rgba(0, 0, 0, 0.03) 0px 10px 24px, rgba(0, 0, 0, 0.04) 0px 18px 40px, rgba(0, 0, 0, 0.05) 0px 30px 64px;
--erp-shadow-xl: rgba(0, 0, 0, 0.01) 0px 3px 6px, rgba(0, 0, 0, 0.02) 0px 8px 16px, rgba(0, 0, 0, 0.03) 0px 14px 30px, rgba(0, 0, 0, 0.04) 0px 22px 48px, rgba(0, 0, 0, 0.06) 0px 36px 80px;
/* Radius */
--erp-radius-sm: 6px;
--erp-radius-md: 8px;
--erp-radius-lg: 12px;
--erp-radius-xl: 16px;
/* Radius — Notion subtle roundness */
--erp-radius-sm: 2px;
--erp-radius-md: 4px;
--erp-radius-lg: 8px;
--erp-radius-xl: 12px;
/* Spacing */
/* Spacing — 8px base unit */
--erp-space-xs: 4px;
--erp-space-sm: 8px;
--erp-space-md: 16px;
@@ -68,9 +68,9 @@
--erp-space-xl: 32px;
--erp-space-2xl: 48px;
/* Typography */
--erp-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC',
'Microsoft YaHei', 'Hiragino Sans GB', 'Helvetica Neue', Helvetica, Arial, sans-serif;
/* Typography — Inter as primary */
--erp-font-family: 'Inter', -apple-system, system-ui, 'Segoe UI', 'PingFang SC',
'Microsoft YaHei', 'Hiragino Sans GB', Helvetica, Arial, sans-serif;
--erp-font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
--erp-font-size-xs: 12px;
@@ -86,10 +86,10 @@
--erp-transition-base: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
--erp-transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* Trend Colors */
--erp-trend-up: #059669;
--erp-trend-down: #DC2626;
--erp-trend-neutral: #64748B;
/* Trend Colors — Warm */
--erp-trend-up: #1aae39;
--erp-trend-down: #e5534b;
--erp-trend-neutral: #615d59;
/* Line Height */
--erp-line-height-tight: 1.25;
@@ -102,36 +102,48 @@
--erp-header-height: 56px;
}
/* --- Dark Mode Tokens --- */
/* --- Dark Mode Tokens — Warm dark, Notion-inspired --- */
[data-theme='dark'] {
--erp-bg-page: #0B0F1A;
--erp-bg-container: #111827;
--erp-bg-elevated: #1E293B;
--erp-bg-spotlight: #1E293B;
--erp-bg-sidebar: #070B14;
--erp-bg-sidebar-hover: #111827;
--erp-primary-light: rgba(0, 117, 222, 0.15);
--erp-primary-light-hover: rgba(0, 117, 222, 0.22);
--erp-primary-bg-subtle: rgba(0, 117, 222, 0.1);
--erp-text-primary: #F1F5F9;
--erp-text-secondary: #94A3B8;
--erp-text-tertiary: #64748B;
--erp-bg-page: #191918;
--erp-bg-container: #232322;
--erp-bg-elevated: #2a2a29;
--erp-bg-spotlight: #2a2a29;
--erp-bg-sidebar: #1e1e1d;
--erp-bg-sidebar-hover: #2a2a29;
--erp-border: #1E293B;
--erp-border-light: #1E293B;
--erp-text-primary: rgba(255, 255, 255, 0.95);
--erp-text-secondary: #a39e98;
--erp-text-tertiary: #6d6862;
--erp-text-sidebar: #a39e98;
--erp-text-sidebar-active: #62aef0;
--erp-shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--erp-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.3);
--erp-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
--erp-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.3);
--erp-border: rgba(255, 255, 255, 0.08);
--erp-border-light: rgba(255, 255, 255, 0.05);
--erp-border-dark: rgba(255, 255, 255, 0.12);
--erp-trend-up: #34D399;
--erp-trend-down: #F87171;
--erp-trend-neutral: #94A3B8;
--erp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
--erp-shadow-sm: rgba(0, 0, 0, 0.2) 0px 4px 18px, rgba(0, 0, 0, 0.15) 0px 2px 8px;
--erp-shadow-md: rgba(0, 0, 0, 0.15) 0px 1px 3px, rgba(0, 0, 0, 0.2) 0px 5px 12px, rgba(0, 0, 0, 0.2) 0px 10px 24px;
--erp-shadow-lg: rgba(0, 0, 0, 0.2) 0px 2px 6px, rgba(0, 0, 0, 0.25) 0px 8px 20px, rgba(0, 0, 0, 0.3) 0px 16px 40px;
--erp-trend-up: #2a9d99;
--erp-trend-down: #e5534b;
--erp-trend-neutral: #a39e98;
--erp-success-bg: rgba(26, 174, 57, 0.15);
--erp-warning-bg: rgba(221, 91, 0, 0.15);
--erp-error-bg: rgba(229, 83, 75, 0.15);
--erp-info-bg: rgba(0, 117, 222, 0.15);
}
[data-theme='dark'] .erp-stat-card-trend-up { color: #34D399; }
[data-theme='dark'] .erp-stat-card-trend-down { color: #FCA5A5; }
[data-theme='dark'] .erp-stat-card-trend-neutral { color: #94A3B8; }
[data-theme='dark'] .erp-stat-card-trend-label { color: #94A3B8; }
[data-theme='dark'] .erp-stat-card-trend-up { color: #2a9d99; }
[data-theme='dark'] .erp-stat-card-trend-down { color: #e5534b; }
[data-theme='dark'] .erp-stat-card-trend-neutral { color: #a39e98; }
[data-theme='dark'] .erp-stat-card-trend-label { color: #a39e98; }
/* --- Global Reset & Base --- */
body {
@@ -170,20 +182,20 @@ body {
/* --- Selection --- */
::selection {
background-color: var(--erp-primary-light);
color: var(--erp-primary);
background-color: rgba(0, 117, 222, 0.15);
color: var(--erp-text-primary);
}
/* ====================================================================
* Component Overrides — Ant Design Enhancement
* ==================================================================== */
/* --- Card --- */
/* --- Card — Whisper border, soft shadow --- */
.ant-card {
border-radius: var(--erp-radius-lg) !important;
border: 1px solid var(--erp-border-light) !important;
box-shadow: var(--erp-shadow-xs) !important;
transition: box-shadow var(--erp-transition-base), transform var(--erp-transition-base) !important;
border: 1px solid var(--erp-border) !important;
box-shadow: none !important;
transition: box-shadow var(--erp-transition-base) !important;
}
.ant-card:hover {
@@ -209,15 +221,14 @@ body {
/* --- Statistic Cards --- */
.stat-card {
border-radius: var(--erp-radius-lg) !important;
border: none !important;
border: 1px solid var(--erp-border) !important;
overflow: hidden;
position: relative;
transition: all var(--erp-transition-base) !important;
transition: box-shadow var(--erp-transition-base) !important;
}
.stat-card:hover {
transform: translateY(-2px) !important;
box-shadow: var(--erp-shadow-md) !important;
box-shadow: var(--erp-shadow-sm) !important;
}
.stat-card .ant-statistic-title {
@@ -259,30 +270,29 @@ body {
border-bottom: 1px solid var(--erp-border-light) !important;
}
/* --- Button --- */
/* --- Button — Subtle 4px radius, no heavy shadow --- */
.ant-btn-primary {
border-radius: var(--erp-radius-md) !important;
border-radius: 4px !important;
font-weight: 500 !important;
box-shadow: 0 1px 2px 0 rgba(79, 70, 229, 0.3) !important;
box-shadow: none !important;
transition: all var(--erp-transition-fast) !important;
}
.ant-btn-primary:hover {
box-shadow: 0 2px 4px 0 rgba(79, 70, 229, 0.4) !important;
transform: translateY(-1px);
opacity: 0.9;
}
.ant-btn-default {
border-radius: var(--erp-radius-md) !important;
border-radius: 4px !important;
font-weight: 500 !important;
}
/* --- Input --- */
/* --- Input — 4px radius, whisper border --- */
.ant-input,
.ant-input-affix-wrapper,
.ant-select-selector,
.ant-picker {
border-radius: var(--erp-radius-md) !important;
border-radius: 4px !important;
transition: all var(--erp-transition-fast) !important;
}
@@ -297,7 +307,7 @@ body {
.ant-select-focused .ant-select-selector,
.ant-picker-focused {
border-color: var(--erp-primary) !important;
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.12) !important;
box-shadow: 0 0 0 2px rgba(0, 117, 222, 0.12) !important;
}
/* --- Modal --- */
@@ -426,12 +436,12 @@ body {
border-radius: var(--erp-radius-lg) var(--erp-radius-lg) 0 0;
}
.erp-gradient-card.indigo::before { background: linear-gradient(90deg, #4F46E5, #818CF8); }
.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #059669, #34D399); }
.erp-gradient-card.amber::before { background: linear-gradient(90deg, #D97706, #FBBF24); }
.erp-gradient-card.rose::before { background: linear-gradient(90deg, #E11D48, #FB7185); }
.erp-gradient-card.sky::before { background: linear-gradient(90deg, #0284C7, #38BDF8); }
.erp-gradient-card.violet::before { background: linear-gradient(90deg, #7C3AED, #A78BFA); }
.erp-gradient-card.indigo::before { background: linear-gradient(90deg, #0075de, #62aef0); }
.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #1aae39, #4ade80); }
.erp-gradient-card.amber::before { background: linear-gradient(90deg, #dd5b00, #fbbf24); }
.erp-gradient-card.rose::before { background: linear-gradient(90deg, #e5534b, #f87171); }
.erp-gradient-card.sky::before { background: linear-gradient(90deg, #0075de, #38bdf8); }
.erp-gradient-card.violet::before { background: linear-gradient(90deg, #391c57, #a78bfa); }
/* --- Fade-in Animation --- */
@keyframes erp-fade-in {
@@ -529,23 +539,23 @@ body {
* ==================================================================== */
.erp-sidebar-menu .ant-menu-item {
margin: 2px 8px !important;
border-radius: var(--erp-radius-md) !important;
height: 40px !important;
line-height: 40px !important;
margin: 1px 8px !important;
border-radius: 4px !important;
height: 36px !important;
line-height: 36px !important;
}
.erp-sidebar-menu .ant-menu-item-selected {
background: var(--erp-primary) !important;
color: #fff !important;
background: #f2f9ff !important;
color: #0075de !important;
}
.erp-sidebar-menu .ant-menu-item-selected .anticon {
color: #fff !important;
color: #0075de !important;
}
.erp-sidebar-menu .ant-menu-item:not(.ant-menu-item-selected):hover {
background: var(--erp-bg-sidebar-hover) !important;
background: #f6f5f4 !important;
}
/* Sidebar group label */
@@ -555,17 +565,17 @@ body {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #94A3B8;
color: #a39e98;
}
/* ====================================================================
* MainLayout — CSS classes replacing inline styles
* ==================================================================== */
/* Sider */
/* Sider — White sidebar, Notion style */
.erp-sider-dark {
background: #0F172A !important;
border-right: none !important;
background: #ffffff !important;
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
position: fixed !important;
left: 0;
top: 0;
@@ -575,57 +585,66 @@ body {
}
[data-theme='dark'] .erp-sider-dark {
background: #070B14 !important;
background: #1e1e1d !important;
border-right: 1px solid rgba(255, 255, 255, 0.05) !important;
}
/* Logo */
/* Logo — Warm neutral, Notion style */
.erp-sidebar-logo {
height: 56px;
display: flex;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
cursor: pointer;
}
[data-theme='dark'] .erp-sidebar-logo {
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.ant-layout-sider-collapsed .erp-sidebar-logo {
justify-content: center;
padding: 0;
}
.erp-sidebar-logo-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: linear-gradient(135deg, #4F46E5, #818CF8);
width: 28px;
height: 28px;
border-radius: 4px;
background: #0075de;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 14px;
font-size: 13px;
font-weight: 800;
color: #fff;
}
.erp-sidebar-logo-text {
margin-left: 12px;
color: #F8FAFC;
font-size: 16px;
margin-left: 10px;
color: rgba(0, 0, 0, 0.95);
font-size: 15px;
font-weight: 700;
letter-spacing: -0.3px;
white-space: nowrap;
}
/* Sidebar menu item */
[data-theme='dark'] .erp-sidebar-logo-text {
color: rgba(255, 255, 255, 0.95);
}
/* Sidebar menu item — White sidebar, warm text */
.erp-sidebar-item {
display: flex;
align-items: center;
height: 40px;
margin: 2px 8px;
padding: 0 16px;
border-radius: 8px;
height: 36px;
margin: 1px 8px;
padding: 0 12px;
border-radius: 4px;
cursor: pointer;
color: #94A3B8;
color: #615d59;
font-size: 14px;
font-weight: 400;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
@@ -638,14 +657,28 @@ body {
}
.erp-sidebar-item:hover:not(.erp-sidebar-item-active) {
background: rgba(255, 255, 255, 0.06);
color: #E2E8F0;
background: #f6f5f4;
color: rgba(0, 0, 0, 0.95);
}
[data-theme='dark'] .erp-sidebar-item {
color: #a39e98;
}
[data-theme='dark'] .erp-sidebar-item:hover:not(.erp-sidebar-item-active) {
background: #2a2a29;
color: rgba(255, 255, 255, 0.95);
}
.erp-sidebar-item-active {
background: linear-gradient(135deg, #4F46E5, #6366F1);
color: #fff;
font-weight: 600;
background: #f2f9ff;
color: #0075de;
font-weight: 500;
}
[data-theme='dark'] .erp-sidebar-item-active {
background: rgba(0, 117, 222, 0.15);
color: #62aef0;
}
.erp-sidebar-item-icon {
@@ -658,17 +691,17 @@ body {
margin-left: 12px;
}
/* Sidebar sub-menu (plugin group) */
/* Sidebar sub-menu (plugin group) — Warm gray group headers */
.erp-sidebar-submenu-title {
display: flex;
align-items: center;
height: 32px;
margin: 6px 8px 2px 8px;
padding: 0 12px;
border-radius: 6px;
border-radius: 4px;
cursor: pointer;
color: #94A3B8;
font-size: 12px;
color: #a39e98;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -677,12 +710,25 @@ body {
}
.erp-sidebar-submenu-title:hover {
background: rgba(255, 255, 255, 0.06);
color: #E2E8F0;
background: #f6f5f4;
color: #615d59;
}
[data-theme='dark'] .erp-sidebar-submenu-title {
color: #6d6862;
}
[data-theme='dark'] .erp-sidebar-submenu-title:hover {
background: #2a2a29;
color: #a39e98;
}
.erp-sidebar-submenu-title-active {
color: #A5B4FC;
color: #0075de;
}
[data-theme='dark'] .erp-sidebar-submenu-title-active {
color: #62aef0;
}
.erp-sidebar-submenu-arrow {
@@ -707,10 +753,10 @@ body {
transition: margin-left 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.erp-main-layout-light { background: #F1F5F9; }
.erp-main-layout-dark { background: #0B0F1A; }
.erp-main-layout-light { background: #f6f5f4; }
.erp-main-layout-dark { background: #191918; }
/* Header */
/* Header — Clean white, whisper border bottom */
.erp-header {
height: 56px !important;
padding: 0 24px !important;
@@ -724,44 +770,44 @@ body {
}
.erp-header-light {
background: #FFFFFF !important;
border-bottom: 1px solid #F1F5F9;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
background: #ffffff !important;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: none;
}
.erp-header-dark {
background: #111827 !important;
border-bottom: 1px solid #1E293B;
background: #232322 !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: none;
}
.erp-header-btn {
width: 36px;
height: 36px;
border-radius: 8px;
width: 32px;
height: 32px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.15s ease;
color: #94A3B8;
color: #615d59;
will-change: background;
}
.erp-header-light .erp-header-btn { color: #64748B; }
.erp-header-dark .erp-header-btn { color: #94A3B8; }
.erp-header-btn:hover { background: #F1F5F9; }
.erp-header-dark .erp-header-btn:hover { background: #1E293B; }
.erp-header-light .erp-header-btn { color: #615d59; }
.erp-header-dark .erp-header-btn { color: #a39e98; }
.erp-header-btn:hover { background: #f6f5f4; }
.erp-header-dark .erp-header-btn:hover { background: #2a2a29; }
.erp-header-title { font-size: 15px; font-weight: 600; }
.erp-text-light { color: #0F172A; }
.erp-text-dark { color: #F1F5F9; }
.erp-text-light-secondary { color: #334155; }
.erp-text-dark-secondary { color: #E2E8F0; }
.erp-text-light { color: rgba(0, 0, 0, 0.95); }
.erp-text-dark { color: rgba(255, 255, 255, 0.95); }
.erp-text-light-secondary { color: #615d59; }
.erp-text-dark-secondary { color: #a39e98; }
.erp-header-divider { width: 1px; height: 24px; margin: 0 8px; }
.erp-header-divider-light { background: #E2E8F0; }
.erp-header-divider-dark { background: #1E293B; }
.erp-header-divider-light { background: rgba(0, 0, 0, 0.06); }
.erp-header-divider-dark { background: rgba(255, 255, 255, 0.05); }
/* User avatar */
.erp-header-user {
@@ -774,11 +820,11 @@ body {
transition: all 0.15s ease;
}
.erp-header-user:hover { background: #F1F5F9; }
.erp-header-dark .erp-header-user:hover { background: #1E293B; }
.erp-header-user:hover { background: #f6f5f4; }
.erp-header-dark .erp-header-user:hover { background: #2a2a29; }
.erp-user-avatar {
background: linear-gradient(135deg, #4F46E5, #818CF8) !important;
background: #0075de !important;
font-size: 13px !important;
}
@@ -786,8 +832,8 @@ body {
/* Footer */
.erp-footer { text-align: center; padding: 12px 24px !important; background: transparent !important; font-size: 12px; }
.erp-footer-light { color: #475569; }
.erp-footer-dark { color: #94A3B8; }
.erp-footer-light { color: #a39e98; }
.erp-footer-dark { color: #6d6862; }
/* ====================================================================
* Dashboard — Stat Cards & Quick Actions (replacing inline styles)
@@ -818,7 +864,7 @@ body {
left: 0;
right: 0;
height: 3px;
background: var(--card-gradient, linear-gradient(135deg, #4F46E5, #6366F1));
background: var(--card-gradient, linear-gradient(135deg, #0075de, #62aef0));
}
.erp-stat-card-body {
@@ -849,7 +895,7 @@ body {
width: 48px;
height: 48px;
border-radius: var(--erp-radius-lg);
background: var(--card-icon-bg, rgba(79, 70, 229, 0.12));
background: var(--card-icon-bg, rgba(0, 117, 222, 0.08));
display: flex;
align-items: center;
justify-content: center;
@@ -867,7 +913,7 @@ body {
.erp-section-icon {
font-size: 16px;
color: #4F46E5;
color: #0075de;
}
.erp-section-title {
@@ -890,17 +936,17 @@ body {
}
.erp-quick-action:hover {
background: #EEF2FF;
border-color: var(--action-color, #4F46E5);
background: #f2f9ff;
border-color: var(--action-color, #0075de);
}
[data-theme='dark'] .erp-quick-action {
background: #0B0F1A;
background: #191918;
}
[data-theme='dark'] .erp-quick-action:hover {
background: #1E293B;
border-color: var(--action-color, #4F46E5);
background: #2a2a29;
border-color: var(--action-color, #0075de);
}
.erp-quick-action-icon {
@@ -910,8 +956,8 @@ body {
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--action-color, #4F46E5) 10%, transparent);
color: var(--action-color, #4F46E5);
background: color-mix(in srgb, var(--action-color, #0075de) 8%, transparent);
color: var(--action-color, #0075de);
font-size: 16px;
flex-shrink: 0;
}
@@ -1000,8 +1046,8 @@ body {
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--action-color, #4F46E5) 10%, transparent);
color: var(--action-color, #4F46E5);
background: color-mix(in srgb, var(--action-color, #0075de) 8%, transparent);
color: var(--action-color, #0075de);
font-size: 18px;
flex-shrink: 0;
transition: transform 0.15s ease;
@@ -1029,7 +1075,7 @@ body {
padding: 12px 16px;
border-radius: var(--erp-radius-md);
background: var(--erp-bg-spotlight);
border-left: 3px solid var(--task-color, #4F46E5);
border-left: 3px solid var(--task-color, #0075de);
cursor: pointer;
transition: all 0.15s ease;
}
@@ -1046,8 +1092,8 @@ body {
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--task-color, #4F46E5) 12%, transparent);
color: var(--task-color, #4F46E5);
background: color-mix(in srgb, var(--task-color, #0075de) 8%, transparent);
color: var(--task-color, #0075de);
font-size: 14px;
flex-shrink: 0;
}
@@ -1069,7 +1115,7 @@ body {
gap: 12px;
margin-top: 2px;
font-size: var(--erp-font-size-xs);
color: #64748B;
color: #a39e98;
}
.erp-task-priority {
@@ -1081,13 +1127,13 @@ body {
font-weight: 600;
}
.erp-task-priority-high { background: #FEF2F2; color: #B91C1C; }
.erp-task-priority-medium { background: #FFFBEB; color: #92400E; }
.erp-task-priority-low { background: #ECFDF5; color: #047857; }
.erp-task-priority-high { background: #fef2f2; color: #e5534b; }
.erp-task-priority-medium { background: #fff7ed; color: #dd5b00; }
.erp-task-priority-low { background: #ecfdf5; color: #1aae39; }
[data-theme='dark'] .erp-task-priority-high { background: rgba(185, 28, 28, 0.15); color: #FCA5A5; }
[data-theme='dark'] .erp-task-priority-medium { background: rgba(146, 64, 14, 0.15); color: #FCD34D; }
[data-theme='dark'] .erp-task-priority-low { background: rgba(4, 120, 87, 0.15); color: #6EE7B7; }
[data-theme='dark'] .erp-task-priority-high { background: rgba(229, 83, 75, 0.15); color: #e5534b; }
[data-theme='dark'] .erp-task-priority-medium { background: rgba(221, 91, 0, 0.15); color: #dd5b00; }
[data-theme='dark'] .erp-task-priority-low { background: rgba(26, 174, 57, 0.15); color: #1aae39; }
/* Activity Timeline */
.erp-activity-list {
@@ -1143,12 +1189,12 @@ body {
.erp-activity-time {
font-size: 11px;
color: #64748B;
color: #a39e98;
margin-top: 2px;
}
[data-theme='dark'] .erp-activity-time {
color: #94A3B8;
color: #6d6862;
}
/* Empty State */

View File

@@ -167,7 +167,7 @@ export default function Home() {
title: '用户总数',
value: stats.userCount,
icon: <UserOutlined />,
gradient: 'linear-gradient(135deg, #4F46E5, #6366F1)',
gradient: 'linear-gradient(135deg, #0075de, #62aef0)',
iconBg: 'rgba(79, 70, 229, 0.12)',
delay: 'erp-fade-in erp-fade-in-delay-1',
trend: { value: '+2', direction: 'up', label: '较上周' },
@@ -179,7 +179,7 @@ export default function Home() {
title: '角色数量',
value: stats.roleCount,
icon: <SafetyCertificateOutlined />,
gradient: 'linear-gradient(135deg, #059669, #10B981)',
gradient: 'linear-gradient(135deg, #1aae39, #10B981)',
iconBg: 'rgba(5, 150, 105, 0.12)',
delay: 'erp-fade-in erp-fade-in-delay-2',
trend: { value: '+1', direction: 'up', label: '较上月' },
@@ -191,7 +191,7 @@ export default function Home() {
title: '流程实例',
value: stats.processInstanceCount,
icon: <FileTextOutlined />,
gradient: 'linear-gradient(135deg, #D97706, #F59E0B)',
gradient: 'linear-gradient(135deg, #dd5b00, #F59E0B)',
iconBg: 'rgba(217, 119, 6, 0.12)',
delay: 'erp-fade-in erp-fade-in-delay-3',
trend: { value: '0', direction: 'neutral', label: '较昨日' },
@@ -213,18 +213,18 @@ export default function Home() {
];
const quickActions = [
{ icon: <UserOutlined />, label: '用户管理', path: '/users', color: '#4F46E5' },
{ icon: <SafetyCertificateOutlined />, label: '权限管理', path: '/roles', color: '#059669' },
{ icon: <ApartmentOutlined />, label: '组织架构', path: '/organizations', color: '#D97706' },
{ icon: <UserOutlined />, label: '用户管理', path: '/users', color: '#0075de' },
{ icon: <SafetyCertificateOutlined />, label: '权限管理', path: '/roles', color: '#1aae39' },
{ icon: <ApartmentOutlined />, label: '组织架构', path: '/organizations', color: '#dd5b00' },
{ icon: <PartitionOutlined />, label: '工作流', path: '/workflow', color: '#7C3AED' },
{ icon: <BellOutlined />, label: '消息中心', path: '/messages', color: '#E11D48' },
{ icon: <SettingOutlined />, label: '系统设置', path: '/settings', color: '#64748B' },
{ icon: <SettingOutlined />, label: '系统设置', path: '/settings', color: '#615d59' },
];
const pendingTasks: TaskItem[] = [
{ id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#DC2626', icon: <UserOutlined />, path: '/users' },
{ id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#D97706', icon: <PartitionOutlined />, path: '/workflow' },
{ id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#059669', icon: <SafetyCertificateOutlined />, path: '/roles' },
{ id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#e5534b', icon: <UserOutlined />, path: '/users' },
{ id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#dd5b00', icon: <PartitionOutlined />, path: '/workflow' },
{ id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#1aae39', icon: <SafetyCertificateOutlined />, path: '/roles' },
];
const recentActivities: ActivityItem[] = [
@@ -243,13 +243,13 @@ export default function Home() {
<h2 style={{
fontSize: 24,
fontWeight: 700,
color: isDark ? '#F1F5F9' : '#0F172A',
color: isDark ? '#f6f5f4' : 'rgba(0,0,0,0.95)',
margin: '0 0 4px',
letterSpacing: '-0.5px',
}}>
</h2>
<p style={{ fontSize: 14, color: isDark ? '#94A3B8' : '#475569', margin: 0 }}>
<p style={{ fontSize: 14, color: isDark ? '#a39e98' : '#615d59', margin: 0 }}>
</p>
</div>
@@ -313,7 +313,7 @@ export default function Home() {
<span style={{
marginLeft: 'auto',
fontSize: 12,
color: isDark ? '#94A3B8' : '#64748B',
color: isDark ? '#a39e98' : '#615d59',
}}>
{pendingTasks.length}
</span>
@@ -340,7 +340,7 @@ export default function Home() {
<span className={`erp-task-priority erp-task-priority-${task.priority}`}>
{priorityLabel[task.priority]}
</span>
<RightOutlined style={{ color: isDark ? '#475569' : '#CBD5E1', fontSize: 12 }} />
<RightOutlined style={{ color: isDark ? '#615d59' : '#CBD5E1', fontSize: 12 }} />
</div>
))}
</div>
@@ -351,7 +351,7 @@ export default function Home() {
<Col xs={24} lg={10}>
<div className="erp-content-card erp-fade-in erp-fade-in-delay-3" style={{ height: '100%' }}>
<div className="erp-section-header">
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#6366F1' }} />
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#62aef0' }} />
<span className="erp-section-title"></span>
</div>
<div className="erp-activity-list">
@@ -400,7 +400,7 @@ export default function Home() {
<Col xs={24} lg={8}>
<div className="erp-content-card erp-fade-in erp-fade-in-delay-4" style={{ height: '100%' }}>
<div className="erp-section-header">
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#6366F1' }} />
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#62aef0' }} />
<span className="erp-section-title"></span>
</div>
<div className="erp-system-info-list">

View File

@@ -30,7 +30,7 @@ export default function Login() {
<div
style={{
flex: 1,
background: 'linear-gradient(135deg, #312E81 0%, #4F46E5 50%, #6366F1 100%)',
background: 'linear-gradient(135deg, #312E81 0%, #0075de 50%, #62aef0 100%)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
@@ -151,7 +151,7 @@ export default function Login() {
<h2 style={{ marginBottom: 4, fontWeight: 700, fontSize: 24 }}>
</h2>
<p style={{ fontSize: 14, color: '#64748B' }}>
<p style={{ fontSize: 14, color: '#615d59' }}>
</p>
@@ -163,7 +163,7 @@ export default function Login() {
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input
prefix={<UserOutlined style={{ color: '#94A3B8' }} />}
prefix={<UserOutlined style={{ color: '#a39e98' }} />}
placeholder="用户名"
style={{ height: 44, borderRadius: 10 }}
/>
@@ -173,7 +173,7 @@ export default function Login() {
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password
prefix={<LockOutlined style={{ color: '#94A3B8' }} />}
prefix={<LockOutlined style={{ color: '#a39e98' }} />}
placeholder="密码"
style={{ height: 44, borderRadius: 10 }}
/>
@@ -197,7 +197,7 @@ export default function Login() {
</Form>
<div style={{ marginTop: 32, textAlign: 'center' }}>
<p style={{ fontSize: 12, color: '#64748B', margin: 0 }}>
<p style={{ fontSize: 12, color: '#615d59', margin: 0 }}>
ERP Platform v0.1.0 · Powered by Rust + React
</p>
</div>

View File

@@ -44,7 +44,7 @@ export default function Organizations() {
const cardStyle = {
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
};
// --- Org tree state ---
@@ -264,9 +264,9 @@ export default function Organizations() {
{item.name}{' '}
{item.code && <Tag style={{
marginLeft: 4,
background: isDark ? '#1E293B' : '#EEF2FF',
background: isDark ? '#1e1e1d' : '#f2f9ff',
border: 'none',
color: '#4F46E5',
color: '#0075de',
fontSize: 11,
}}>{item.code}</Tag>}
</span>
@@ -282,9 +282,9 @@ export default function Organizations() {
{item.name}{' '}
{item.code && <Tag style={{
marginLeft: 4,
background: isDark ? '#1E293B' : '#ECFDF5',
background: isDark ? '#1e1e1d' : '#ECFDF5',
border: 'none',
color: '#059669',
color: '#1aae39',
fontSize: 11,
}}>{item.code}</Tag>}
</span>
@@ -343,7 +343,7 @@ export default function Organizations() {
<div className="erp-page-header">
<div>
<h4>
<ApartmentOutlined style={{ marginRight: 8, color: '#4F46E5' }} />
<ApartmentOutlined style={{ marginRight: 8, color: '#0075de' }} />
</h4>
<div className="erp-page-subtitle"></div>
@@ -356,7 +356,7 @@ export default function Organizations() {
<div style={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
<div style={{
padding: '14px 20px',
borderBottom: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
borderBottom: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@@ -418,7 +418,7 @@ export default function Organizations() {
<div style={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
<div style={{
padding: '14px 20px',
borderBottom: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
borderBottom: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@@ -471,7 +471,7 @@ export default function Organizations() {
<div style={{ flex: 1, ...cardStyle, overflow: 'hidden' }}>
<div style={{
padding: '14px 20px',
borderBottom: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
borderBottom: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',

View File

@@ -41,11 +41,11 @@ import {
import PluginSettingsForm from '../components/PluginSettingsForm';
const STATUS_CONFIG: Record<PluginStatus, { color: string; label: string }> = {
uploaded: { color: '#64748B', label: '已上传' },
uploaded: { color: '#615d59', label: '已上传' },
installed: { color: '#2563EB', label: '已安装' },
enabled: { color: '#059669', label: '已启用' },
running: { color: '#059669', label: '运行中' },
disabled: { color: '#DC2626', label: '已禁用' },
enabled: { color: '#1aae39', label: '已启用' },
running: { color: '#1aae39', label: '运行中' },
disabled: { color: '#e5534b', label: '已禁用' },
uninstalled: { color: '#9333EA', label: '已卸载' },
};
@@ -215,7 +215,7 @@ export default function PluginAdmin() {
key: 'status',
width: 100,
render: (status: PluginStatus) => {
const cfg = STATUS_CONFIG[status] || { color: '#64748B', label: status };
const cfg = STATUS_CONFIG[status] || { color: '#615d59', label: status };
return <Tag color={cfg.color}>{cfg.label}</Tag>;
},
},

View File

@@ -299,7 +299,7 @@ export function PluginDashboardPage() {
style={{
fontSize: 24,
fontWeight: 700,
color: isDark ? '#F1F5F9' : '#0F172A',
color: isDark ? '#f6f5f4' : 'rgba(0,0,0,0.95)',
margin: '0 0 4px',
letterSpacing: '-0.5px',
}}
@@ -309,7 +309,7 @@ export function PluginDashboardPage() {
<p
style={{
fontSize: 14,
color: isDark ? '#94A3B8' : '#475569',
color: isDark ? '#a39e98' : '#615d59',
margin: 0,
}}
>
@@ -352,7 +352,7 @@ export function PluginDashboardPage() {
<div className="erp-section-header">
<DashboardOutlined
className="erp-section-icon"
style={{ color: '#4F46E5' }}
style={{ color: '#0075de' }}
/>
<span className="erp-section-title"></span>
</div>
@@ -389,7 +389,7 @@ export function PluginDashboardPage() {
<div className="erp-section-header">
<DashboardOutlined
className="erp-section-icon"
style={{ color: currentPalette.tagColor === 'purple' ? '#4F46E5' : '#3B82F6' }}
style={{ color: currentPalette.tagColor === 'purple' ? '#0075de' : '#3B82F6' }}
/>
<span className="erp-section-title">
{currentEntity?.display_name || selectedEntity}

View File

@@ -313,8 +313,8 @@ export function PluginGraphPage() {
const r = degreeToRadius(degree, isCenter);
// Determine node color from its most common edge type, or default palette
let nodeColorBase = '#4F46E5';
let nodeColorLight = '#818CF8';
let nodeColorBase = '#0075de';
let nodeColorLight = '#62aef0';
let nodeColorGlow = 'rgba(79,70,229,0.3)';
if (isCenter) {

View File

@@ -37,12 +37,12 @@ import {
const { Title, Text, Paragraph } = Typography;
const CATEGORY_COLORS: Record<string, string> = {
'财务': '#059669',
'财务': '#1aae39',
'CRM': '#2563EB',
'进销存': '#9333EA',
'生产': '#DC2626',
'人力资源': '#D97706',
'基础': '#64748B',
'生产': '#e5534b',
'人力资源': '#dd5b00',
'基础': '#615d59',
};
export default function PluginMarket() {
@@ -190,7 +190,7 @@ export default function PluginMarket() {
<div style={{ marginBottom: 8 }}>
<Text strong style={{ fontSize: 16 }}>{plugin.name}</Text>
<Tag
color={CATEGORY_COLORS[plugin.category ?? ''] ?? '#64748B'}
color={CATEGORY_COLORS[plugin.category ?? ''] ?? '#615d59'}
style={{ marginLeft: 8 }}
>
{plugin.category}
@@ -244,7 +244,7 @@ export default function PluginMarket() {
<div>
<div style={{ marginBottom: 16 }}>
<Space>
<Tag color={CATEGORY_COLORS[selectedPlugin.category ?? ''] ?? '#64748B'}>
<Tag color={CATEGORY_COLORS[selectedPlugin.category ?? ''] ?? '#615d59'}>
{selectedPlugin.category}
</Tag>
<Text type="secondary">v{selectedPlugin.version}</Text>

View File

@@ -153,12 +153,12 @@ export default function Roles() {
height: 32,
borderRadius: 8,
background: record.is_system
? 'linear-gradient(135deg, #4F46E5, #818CF8)'
: isDark ? '#1E293B' : '#F1F5F9',
? 'linear-gradient(135deg, #0075de, #62aef0)'
: isDark ? '#1e1e1d' : '#f6f5f4',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: record.is_system ? '#fff' : isDark ? '#94A3B8' : '#64748B',
color: record.is_system ? '#fff' : isDark ? '#a39e98' : '#615d59',
fontSize: 14,
}}
>
@@ -174,9 +174,9 @@ export default function Roles() {
key: 'code',
render: (v: string) => (
<Tag style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1e1e1d' : '#f6f5f4',
border: 'none',
color: isDark ? '#94A3B8' : '#64748B',
color: isDark ? '#a39e98' : '#615d59',
fontFamily: 'monospace',
fontSize: 12,
}}>
@@ -190,7 +190,7 @@ export default function Roles() {
key: 'description',
ellipsis: true,
render: (v: string | undefined) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8' }}>{v || '-'}</span>
<span style={{ color: isDark ? '#615d59' : '#a39e98' }}>{v || '-'}</span>
),
},
{
@@ -201,8 +201,8 @@ export default function Roles() {
render: (v: boolean) => (
<Tag
style={{
color: v ? '#4F46E5' : (isDark ? '#94A3B8' : '#64748B'),
background: v ? '#EEF2FF' : (isDark ? '#1E293B' : '#F1F5F9'),
color: v ? '#0075de' : (isDark ? '#a39e98' : '#615d59'),
background: v ? '#f2f9ff' : (isDark ? '#1e1e1d' : '#f6f5f4'),
border: 'none',
fontWeight: 500,
}}
@@ -222,7 +222,7 @@ export default function Roles() {
type="text"
icon={<SafetyCertificateOutlined />}
onClick={() => openPermModal(record)}
style={{ color: '#4F46E5' }}
style={{ color: '#0075de' }}
>
</Button>
@@ -233,7 +233,7 @@ export default function Roles() {
type="text"
icon={<EditOutlined />}
onClick={() => openEditModal(record)}
style={{ color: isDark ? '#94A3B8' : '#64748B' }}
style={{ color: isDark ? '#a39e98' : '#615d59' }}
/>
<Popconfirm
title="确定删除此角色?"
@@ -279,7 +279,7 @@ export default function Roles() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table
@@ -336,8 +336,8 @@ export default function Roles() {
marginBottom: 16,
padding: 16,
borderRadius: 10,
border: `1px solid ${isDark ? '#1E293B' : '#E2E8F0'}`,
background: isDark ? '#0B0F1A' : '#F8FAFC',
border: `1px solid ${isDark ? '#1e1e1d' : '#E2E8F0'}`,
background: isDark ? '#0B0F1A' : '#fafaf9',
}}
>
<div style={{

View File

@@ -35,9 +35,9 @@ import { listRoles, type RoleInfo } from '../api/roles';
import type { UserInfo } from '../api/auth';
const STATUS_COLOR_MAP: Record<string, string> = {
active: '#059669',
disabled: '#DC2626',
locked: '#D97706',
active: '#1aae39',
disabled: '#e5534b',
locked: '#dd5b00',
};
const STATUS_BG_MAP: Record<string, string> = {
@@ -219,7 +219,7 @@ export default function Users() {
width: 32,
height: 32,
borderRadius: 8,
background: 'linear-gradient(135deg, #4F46E5, #818CF8)',
background: 'linear-gradient(135deg, #0075de, #62aef0)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -233,7 +233,7 @@ export default function Users() {
<div>
<div style={{ fontWeight: 500, fontSize: 14 }}>{v}</div>
{record.display_name && (
<div style={{ fontSize: 12, color: isDark ? '#64748B' : '#94A3B8' }}>
<div style={{ fontSize: 12, color: isDark ? '#615d59' : '#a39e98' }}>
{record.display_name}
</div>
)}
@@ -261,8 +261,8 @@ export default function Users() {
render: (status: string) => (
<Tag
style={{
color: STATUS_COLOR_MAP[status] || '#64748B',
background: STATUS_BG_MAP[status] || '#F1F5F9',
color: STATUS_COLOR_MAP[status] || '#615d59',
background: STATUS_BG_MAP[status] || '#f6f5f4',
border: 'none',
fontWeight: 500,
}}
@@ -279,14 +279,14 @@ export default function Users() {
roles.length > 0
? roles.map((r) => (
<Tag key={r.id} style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1e1e1d' : '#f6f5f4',
border: 'none',
color: isDark ? '#CBD5E1' : '#475569',
color: isDark ? '#CBD5E1' : '#615d59',
}}>
{r.name}
</Tag>
))
: <span style={{ color: isDark ? '#475569' : '#CBD5E1' }}>-</span>,
: <span style={{ color: isDark ? '#615d59' : '#CBD5E1' }}>-</span>,
},
{
title: '操作',
@@ -299,14 +299,14 @@ export default function Users() {
type="text"
icon={<EditOutlined />}
onClick={() => openEditModal(record)}
style={{ color: isDark ? '#94A3B8' : '#64748B' }}
style={{ color: isDark ? '#a39e98' : '#615d59' }}
/>
<Button
size="small"
type="text"
icon={<SafetyCertificateOutlined />}
onClick={() => openRoleModal(record)}
style={{ color: isDark ? '#94A3B8' : '#64748B' }}
style={{ color: isDark ? '#a39e98' : '#615d59' }}
/>
{record.status === 'active' ? (
<Popconfirm
@@ -326,7 +326,7 @@ export default function Users() {
type="text"
icon={<CheckCircleOutlined />}
onClick={() => handleToggleStatus(record.id, 'active')}
style={{ color: '#059669' }}
style={{ color: '#1aae39' }}
/>
)}
<Popconfirm
@@ -356,7 +356,7 @@ export default function Users() {
<Space size={8}>
<Input
placeholder="搜索用户名..."
prefix={<SearchOutlined style={{ color: '#94A3B8' }} />}
prefix={<SearchOutlined style={{ color: '#a39e98' }} />}
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
@@ -379,7 +379,7 @@ export default function Users() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table
@@ -415,7 +415,7 @@ export default function Users() {
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input prefix={<UserOutlined style={{ color: '#94A3B8' }} />} disabled={!!editUser} />
<Input prefix={<UserOutlined style={{ color: '#a39e98' }} />} disabled={!!editUser} />
</Form.Item>
{!editUser && (
<Form.Item
@@ -465,13 +465,13 @@ export default function Users() {
style={{
padding: '10px 14px',
borderRadius: 8,
border: `1px solid ${isDark ? '#1E293B' : '#E2E8F0'}`,
background: isDark ? '#0B0F1A' : '#F8FAFC',
border: `1px solid ${isDark ? '#1e1e1d' : '#E2E8F0'}`,
background: isDark ? '#0B0F1A' : '#fafaf9',
}}
>
<Checkbox value={r.id}>
<span style={{ fontWeight: 500 }}>{r.name}</span>
<span style={{ color: isDark ? '#475569' : '#94A3B8', marginLeft: 8, fontSize: 12 }}>
<span style={{ color: isDark ? '#615d59' : '#a39e98', marginLeft: 8, fontSize: 12 }}>
{r.code}
</span>
</Checkbox>

View File

@@ -46,7 +46,7 @@ function prepareChartData(data: WidgetData['data'], dimensionOrder?: string[]) {
const TAG_COLOR_MAP: Record<string, string> = {
blue: '#3B82F6', green: '#10B981', orange: '#F59E0B', red: '#EF4444',
purple: '#8B5CF6', cyan: '#06B6D4', magenta: '#EC4899', gold: '#EAB308',
lime: '#84CC16', geekblue: '#6366F1', volcano: '#F97316',
lime: '#84CC16', geekblue: '#62aef0', volcano: '#F97316',
};
function tagStrokeColor(color: string): string {
@@ -204,7 +204,7 @@ export function SkeletonBreakdownCard({ index }: { index: number }) {
function StatWidgetCard({ widgetData }: { widgetData: WidgetData }) {
const { widget, count } = widgetData;
const animatedValue = useCountUp(count ?? 0);
const color = widget.color || '#4F46E5';
const color = widget.color || '#0075de';
return (
<Card size="small" className="erp-fade-in" style={{ height: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
@@ -229,7 +229,7 @@ function StatWidgetCard({ widgetData }: { widgetData: WidgetData }) {
function BarWidgetCard({ widgetData, isDark }: { widgetData: WidgetData; isDark: boolean }) {
const { widget, data } = widgetData;
const chartData = prepareChartData(data, widget.dimension_order);
const axisLabelStyle = { fill: isDark ? '#94A3B8' : '#475569' };
const axisLabelStyle = { fill: isDark ? '#a39e98' : '#615d59' };
return (
<WidgetCardShell title={widget.title} widgetType={widget.type}>
{chartData.length > 0 ? (
@@ -275,7 +275,7 @@ function FunnelWidgetCard({ widgetData }: { widgetData: WidgetData }) {
function LineWidgetCard({ widgetData, isDark }: { widgetData: WidgetData; isDark: boolean }) {
const { widget, data } = widgetData;
const chartData = prepareChartData(data, widget.dimension_order);
const axisLabelStyle = { fill: isDark ? '#94A3B8' : '#475569' };
const axisLabelStyle = { fill: isDark ? '#a39e98' : '#615d59' };
return (
<WidgetCardShell title={widget.title} widgetType={widget.type}>
{chartData.length > 0 ? (
@@ -315,7 +315,7 @@ function StatCardsWidget({ widgetData }: { widgetData: WidgetData }) {
{statCards.map((sc, i) => (
<Col xs={12} sm={6} key={`${sc.card.entity}-${sc.card.label}-${i}`}>
<div style={{
background: `${sc.card.color || '#4F46E5'}10`,
background: `${sc.card.color || '#0075de'}10`,
borderRadius: 8,
padding: '12px 16px',
display: 'flex',
@@ -324,9 +324,9 @@ function StatCardsWidget({ widgetData }: { widgetData: WidgetData }) {
}}>
<div style={{
width: 36, height: 36, borderRadius: 8,
background: `${sc.card.color || '#4F46E5'}20`,
background: `${sc.card.color || '#0075de'}20`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: sc.card.color || '#4F46E5', fontSize: 18,
color: sc.card.color || '#0075de', fontSize: 18,
}}>
<DashboardOutlined />
</div>

View File

@@ -19,9 +19,9 @@ import {
// ── 通用调色板 ──
const UNIVERSAL_COLORS = [
{ gradient: 'linear-gradient(135deg, #4F46E5, #6366F1)', iconBg: 'rgba(79, 70, 229, 0.12)', tagColor: 'purple' },
{ gradient: 'linear-gradient(135deg, #059669, #10B981)', iconBg: 'rgba(5, 150, 105, 0.12)', tagColor: 'green' },
{ gradient: 'linear-gradient(135deg, #D97706, #F59E0B)', iconBg: 'rgba(217, 119, 6, 0.12)', tagColor: 'orange' },
{ gradient: 'linear-gradient(135deg, #0075de, #62aef0)', iconBg: 'rgba(79, 70, 229, 0.12)', tagColor: 'purple' },
{ gradient: 'linear-gradient(135deg, #1aae39, #10B981)', iconBg: 'rgba(5, 150, 105, 0.12)', tagColor: 'green' },
{ gradient: 'linear-gradient(135deg, #dd5b00, #F59E0B)', iconBg: 'rgba(217, 119, 6, 0.12)', tagColor: 'orange' },
{ gradient: 'linear-gradient(135deg, #7C3AED, #A78BFA)', iconBg: 'rgba(124, 58, 237, 0.12)', tagColor: 'volcano' },
{ gradient: 'linear-gradient(135deg, #E11D48, #F43F5E)', iconBg: 'rgba(225, 29, 72, 0.12)', tagColor: 'red' },
{ gradient: 'linear-gradient(135deg, #0891B2, #06B6D4)', iconBg: 'rgba(8, 145, 178, 0.12)', tagColor: 'cyan' },

View File

@@ -11,11 +11,11 @@ import type { GraphEdge } from './graphTypes';
/** 关系类型对应的色板 (base / light / glow) — 通用调色板自动分配 */
const EDGE_PALETTE: Array<{ base: string; light: string; glow: string }> = [
{ base: '#4F46E5', light: '#818CF8', glow: 'rgba(79,70,229,0.3)' },
{ base: '#059669', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
{ base: '#D97706', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
{ base: '#0075de', light: '#62aef0', glow: 'rgba(79,70,229,0.3)' },
{ base: '#1aae39', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
{ base: '#dd5b00', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
{ base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
{ base: '#DC2626', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
{ base: '#e5534b', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
{ base: '#7C3AED', light: '#A78BFA', glow: 'rgba(124,58,237,0.3)' },
{ base: '#EA580C', light: '#FB923C', glow: 'rgba(234,88,12,0.3)' },
{ base: '#DB2777', light: '#F472B6', glow: 'rgba(219,39,119,0.3)' },

View File

@@ -5,9 +5,9 @@ import type { ColumnsType } from 'antd/es/table';
import { listTemplates, createTemplate, type MessageTemplateInfo } from '../../api/messageTemplates';
const channelMap: Record<string, { label: string; color: string }> = {
in_app: { label: '站内', color: '#4F46E5' },
email: { label: '邮件', color: '#059669' },
sms: { label: '短信', color: '#D97706' },
in_app: { label: '站内', color: '#0075de' },
email: { label: '邮件', color: '#1aae39' },
sms: { label: '短信', color: '#dd5b00' },
wechat: { label: '微信', color: '#7C3AED' },
};
@@ -64,9 +64,9 @@ export default function MessageTemplates() {
key: 'code',
render: (v: string) => (
<Tag style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1e1e1d' : '#f6f5f4',
border: 'none',
color: isDark ? '#94A3B8' : '#64748B',
color: isDark ? '#a39e98' : '#615d59',
fontFamily: 'monospace',
fontSize: 12,
}}>
@@ -80,7 +80,7 @@ export default function MessageTemplates() {
key: 'channel',
width: 90,
render: (c: string) => {
const info = channelMap[c] || { label: c, color: '#64748B' };
const info = channelMap[c] || { label: c, color: '#615d59' };
return (
<Tag style={{
background: info.color + '15',
@@ -111,7 +111,7 @@ export default function MessageTemplates() {
key: 'created_at',
width: 180,
render: (v: string) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8', fontSize: 13 }}>{v}</span>
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>{v}</span>
),
},
];
@@ -124,7 +124,7 @@ export default function MessageTemplates() {
alignItems: 'center',
marginBottom: 16,
}}>
<span style={{ fontSize: 13, color: isDark ? '#64748B' : '#94A3B8' }}>
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98' }}>
{total}
</span>
<Button type="primary" icon={<PlusOutlined />} onClick={() => setModalOpen(true)}>
@@ -135,7 +135,7 @@ export default function MessageTemplates() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -11,9 +11,9 @@ interface Props {
}
const priorityStyles: Record<string, { bg: string; color: string; text: string }> = {
urgent: { bg: '#FEF2F2', color: '#DC2626', text: '紧急' },
important: { bg: '#FFFBEB', color: '#D97706', text: '重要' },
normal: { bg: '#EEF2FF', color: '#4F46E5', text: '普通' },
urgent: { bg: '#FEF2F2', color: '#e5534b', text: '紧急' },
important: { bg: '#FFFBEB', color: '#dd5b00', text: '重要' },
normal: { bg: '#f2f9ff', color: '#0075de', text: '普通' },
};
export default function NotificationList({ queryFilter }: Props) {
@@ -83,7 +83,7 @@ export default function NotificationList({ queryFilter }: Props) {
content: (
<div>
<Paragraph>{record.body}</Paragraph>
<div style={{ marginTop: 8, color: isDark ? '#475569' : '#94A3B8', fontSize: 12 }}>
<div style={{ marginTop: 8, color: isDark ? '#615d59' : '#a39e98', fontSize: 12 }}>
{record.created_at}
</div>
</div>
@@ -104,7 +104,7 @@ export default function NotificationList({ queryFilter }: Props) {
style={{
fontWeight: record.is_read ? 400 : 600,
cursor: 'pointer',
color: record.is_read ? (isDark ? '#94A3B8' : '#64748B') : 'inherit',
color: record.is_read ? (isDark ? '#a39e98' : '#615d59') : 'inherit',
}}
onClick={() => showDetail(record)}
>
@@ -114,7 +114,7 @@ export default function NotificationList({ queryFilter }: Props) {
width: 6,
height: 6,
borderRadius: '50%',
background: '#4F46E5',
background: '#0075de',
marginRight: 8,
}} />
)}
@@ -128,7 +128,7 @@ export default function NotificationList({ queryFilter }: Props) {
key: 'priority',
width: 90,
render: (p: string) => {
const info = priorityStyles[p] || { bg: '#F1F5F9', color: '#64748B', text: p };
const info = priorityStyles[p] || { bg: '#f6f5f4', color: '#615d59', text: p };
return (
<Tag style={{
background: info.bg,
@@ -146,7 +146,7 @@ export default function NotificationList({ queryFilter }: Props) {
dataIndex: 'sender_type',
key: 'sender_type',
width: 80,
render: (s: string) => <span style={{ color: isDark ? '#64748B' : '#94A3B8' }}>{s === 'system' ? '系统' : '用户'}</span>,
render: (s: string) => <span style={{ color: isDark ? '#615d59' : '#a39e98' }}>{s === 'system' ? '系统' : '用户'}</span>,
},
{
title: '状态',
@@ -155,9 +155,9 @@ export default function NotificationList({ queryFilter }: Props) {
width: 80,
render: (r: boolean) => (
<Tag style={{
background: r ? (isDark ? '#1E293B' : '#F1F5F9') : '#EEF2FF',
background: r ? (isDark ? '#1e1e1d' : '#f6f5f4') : '#f2f9ff',
border: 'none',
color: r ? (isDark ? '#64748B' : '#94A3B8') : '#4F46E5',
color: r ? (isDark ? '#615d59' : '#a39e98') : '#0075de',
fontWeight: 500,
}}>
{r ? '已读' : '未读'}
@@ -170,7 +170,7 @@ export default function NotificationList({ queryFilter }: Props) {
key: 'created_at',
width: 180,
render: (v: string) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8', fontSize: 13 }}>{v}</span>
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>{v}</span>
),
},
{
@@ -185,7 +185,7 @@ export default function NotificationList({ queryFilter }: Props) {
size="small"
icon={<CheckOutlined />}
onClick={() => handleMarkRead(record.id)}
style={{ color: '#4F46E5' }}
style={{ color: '#0075de' }}
/>
)}
<Button
@@ -193,7 +193,7 @@ export default function NotificationList({ queryFilter }: Props) {
size="small"
icon={<EyeOutlined />}
onClick={() => showDetail(record)}
style={{ color: isDark ? '#64748B' : '#94A3B8' }}
style={{ color: isDark ? '#615d59' : '#a39e98' }}
/>
<Button
type="text"
@@ -215,7 +215,7 @@ export default function NotificationList({ queryFilter }: Props) {
alignItems: 'center',
marginBottom: 16,
}}>
<span style={{ fontSize: 13, color: isDark ? '#64748B' : '#94A3B8' }}>
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98' }}>
{total}
</span>
<Button icon={<CheckOutlined />} onClick={handleMarkAllRead}>
@@ -226,7 +226,7 @@ export default function NotificationList({ queryFilter }: Props) {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -48,12 +48,12 @@ export default function NotificationPreferences() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
padding: 24,
maxWidth: 600,
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20 }}>
<BellOutlined style={{ fontSize: 16, color: '#4F46E5' }} />
<BellOutlined style={{ fontSize: 16, color: '#0075de' }} />
<span style={{ fontSize: 15, fontWeight: 600 }}></span>
</div>

View File

@@ -5,11 +5,11 @@
// 通用边调色板
const EDGE_PALETTE: Array<{ base: string; light: string; glow: string }> = [
{ base: '#4F46E5', light: '#818CF8', glow: 'rgba(79,70,229,0.3)' },
{ base: '#059669', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
{ base: '#D97706', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
{ base: '#0075de', light: '#62aef0', glow: 'rgba(79,70,229,0.3)' },
{ base: '#1aae39', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
{ base: '#dd5b00', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
{ base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
{ base: '#DC2626', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
{ base: '#e5534b', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
{ base: '#7C3AED', light: '#A78BFA', glow: 'rgba(124,58,237,0.3)' },
{ base: '#EA580C', light: '#FB923C', glow: 'rgba(234,88,12,0.3)' },
{ base: '#DB2777', light: '#F472B6', glow: 'rgba(219,39,119,0.3)' },

View File

@@ -295,8 +295,8 @@ export function drawFullGraph(
const degree = degreeMap.get(node.id) || 0;
const r = degreeToRadius(degree, isCenter);
let nodeColorBase = '#4F46E5';
let nodeColorLight = '#818CF8';
let nodeColorBase = '#0075de';
let nodeColorLight = '#62aef0';
let nodeColorGlow = 'rgba(79,70,229,0.3)';
if (isCenter) {

View File

@@ -17,9 +17,9 @@ const RESOURCE_TYPE_OPTIONS = [
];
const ACTION_STYLES: Record<string, { bg: string; color: string; text: string }> = {
create: { bg: '#ECFDF5', color: '#059669', text: '创建' },
update: { bg: '#EEF2FF', color: '#4F46E5', text: '更新' },
delete: { bg: '#FEF2F2', color: '#DC2626', text: '删除' },
create: { bg: '#ECFDF5', color: '#1aae39', text: '创建' },
update: { bg: '#f2f9ff', color: '#0075de', text: '更新' },
delete: { bg: '#FEF2F2', color: '#e5534b', text: '删除' },
};
function formatDateTime(value: string): string {
@@ -80,7 +80,7 @@ export default function AuditLogViewer() {
key: 'action',
width: 100,
render: (action: string) => {
const info = ACTION_STYLES[action] || { bg: '#F1F5F9', color: '#64748B', text: action };
const info = ACTION_STYLES[action] || { bg: '#f6f5f4', color: '#615d59', text: action };
return (
<Tag style={{
background: info.bg,
@@ -100,9 +100,9 @@ export default function AuditLogViewer() {
width: 120,
render: (v: string) => (
<Tag style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1e1e1d' : '#f6f5f4',
border: 'none',
color: isDark ? '#CBD5E1' : '#475569',
color: isDark ? '#CBD5E1' : '#615d59',
}}>
{v}
</Tag>
@@ -115,7 +115,7 @@ export default function AuditLogViewer() {
width: 200,
ellipsis: true,
render: (v: string) => (
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#94A3B8' : '#64748B' }}>
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#a39e98' : '#615d59' }}>
{v}
</span>
),
@@ -127,7 +127,7 @@ export default function AuditLogViewer() {
width: 200,
ellipsis: true,
render: (v: string) => (
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#94A3B8' : '#64748B' }}>
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#a39e98' : '#615d59' }}>
{v}
</span>
),
@@ -138,7 +138,7 @@ export default function AuditLogViewer() {
key: 'created_at',
width: 180,
render: (value: string) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8', fontSize: 13 }}>
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
{formatDateTime(value)}
</span>
),
@@ -156,7 +156,7 @@ export default function AuditLogViewer() {
padding: 12,
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 10,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
}}>
<Select
allowClear
@@ -173,7 +173,7 @@ export default function AuditLogViewer() {
value={query.user_id ?? ''}
onChange={(e) => handleFilterChange('user_id', e.target.value)}
/>
<span style={{ fontSize: 13, color: isDark ? '#64748B' : '#94A3B8', marginLeft: 'auto' }}>
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98', marginLeft: 'auto' }}>
{total}
</span>
</div>
@@ -182,7 +182,7 @@ export default function AuditLogViewer() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -132,9 +132,9 @@ export default function SystemSettings() {
width: 250,
render: (v: string) => (
<Tag style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1e1e1d' : '#f6f5f4',
border: 'none',
color: isDark ? '#CBD5E1' : '#475569',
color: isDark ? '#CBD5E1' : '#615d59',
fontFamily: 'monospace',
fontSize: 12,
}}>
@@ -162,7 +162,7 @@ export default function SystemSettings() {
type="text"
icon={<EditOutlined />}
onClick={() => openEdit(record)}
style={{ color: isDark ? '#94A3B8' : '#64748B' }}
style={{ color: isDark ? '#a39e98' : '#615d59' }}
/>
<Popconfirm
title="确定删除此设置?"
@@ -191,7 +191,7 @@ export default function SystemSettings() {
<Space>
<Input
placeholder="输入设置键名查询"
prefix={<SearchOutlined style={{ color: '#94A3B8' }} />}
prefix={<SearchOutlined style={{ color: '#a39e98' }} />}
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
onPressEnter={handleSearch}
@@ -207,7 +207,7 @@ export default function SystemSettings() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -4,9 +4,9 @@ import type { ColumnsType } from 'antd/es/table';
import { listCompletedTasks, type TaskInfo } from '../../api/workflowTasks';
const outcomeStyles: Record<string, { bg: string; color: string; text: string }> = {
approved: { bg: '#ECFDF5', color: '#059669', text: '同意' },
rejected: { bg: '#FEF2F2', color: '#DC2626', text: '拒绝' },
delegated: { bg: '#EEF2FF', color: '#4F46E5', text: '已委派' },
approved: { bg: '#ECFDF5', color: '#1aae39', text: '同意' },
rejected: { bg: '#FEF2F2', color: '#e5534b', text: '拒绝' },
delegated: { bg: '#f2f9ff', color: '#0075de', text: '已委派' },
};
export default function CompletedTasks() {
@@ -50,7 +50,7 @@ export default function CompletedTasks() {
key: 'outcome',
width: 100,
render: (o: string) => {
const info = outcomeStyles[o] || { bg: '#F1F5F9', color: '#64748B', text: o };
const info = outcomeStyles[o] || { bg: '#f6f5f4', color: '#615d59', text: o };
return (
<Tag style={{
background: info.bg,
@@ -69,7 +69,7 @@ export default function CompletedTasks() {
key: 'completed_at',
width: 180,
render: (v: string) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8', fontSize: 13 }}>
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
{v ? new Date(v).toLocaleString() : '-'}
</span>
),
@@ -80,7 +80,7 @@ export default function CompletedTasks() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -13,10 +13,10 @@ import { getProcessDefinition, type NodeDef, type EdgeDef } from '../../api/work
import ProcessViewer from './ProcessViewer';
const statusStyles: Record<string, { bg: string; color: string; text: string }> = {
running: { bg: '#EEF2FF', color: '#4F46E5', text: '运行中' },
suspended: { bg: '#FFFBEB', color: '#D97706', text: '已挂起' },
completed: { bg: '#ECFDF5', color: '#059669', text: '已完成' },
terminated: { bg: '#FEF2F2', color: '#DC2626', text: '已终止' },
running: { bg: '#f2f9ff', color: '#0075de', text: '运行中' },
suspended: { bg: '#FFFBEB', color: '#dd5b00', text: '已挂起' },
completed: { bg: '#ECFDF5', color: '#1aae39', text: '已完成' },
terminated: { bg: '#FEF2F2', color: '#e5534b', text: '已终止' },
};
export default function InstanceMonitor() {
@@ -129,7 +129,7 @@ export default function InstanceMonitor() {
key: 'status',
width: 100,
render: (s: string) => {
const info = statusStyles[s] || { bg: '#F1F5F9', color: '#64748B', text: s };
const info = statusStyles[s] || { bg: '#f6f5f4', color: '#615d59', text: s };
return (
<Tag style={{
background: info.bg,
@@ -154,7 +154,7 @@ export default function InstanceMonitor() {
key: 'started_at',
width: 180,
render: (v: string) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8', fontSize: 13 }}>
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
{new Date(v).toLocaleString()}
</span>
),
@@ -214,7 +214,7 @@ export default function InstanceMonitor() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -76,9 +76,9 @@ export default function PendingTasks() {
key: 'business_key',
render: (v: string | undefined) => v ? (
<Tag style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1e1e1d' : '#f6f5f4',
border: 'none',
color: isDark ? '#94A3B8' : '#64748B',
color: isDark ? '#a39e98' : '#615d59',
fontFamily: 'monospace',
fontSize: 12,
}}>
@@ -93,9 +93,9 @@ export default function PendingTasks() {
width: 100,
render: (s: string) => (
<Tag style={{
background: '#EEF2FF',
background: '#f2f9ff',
border: 'none',
color: '#4F46E5',
color: '#0075de',
fontWeight: 500,
}}>
{s}
@@ -108,7 +108,7 @@ export default function PendingTasks() {
key: 'created_at',
width: 180,
render: (v: string) => (
<span style={{ color: isDark ? '#64748B' : '#94A3B8', fontSize: 13 }}>
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
{new Date(v).toLocaleString()}
</span>
),
@@ -145,7 +145,7 @@ export default function PendingTasks() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table

View File

@@ -13,9 +13,9 @@ import {
import ProcessDesigner from './ProcessDesigner';
const statusColors: Record<string, { bg: string; color: string; text: string }> = {
draft: { bg: '#F1F5F9', color: '#64748B', text: '草稿' },
published: { bg: '#ECFDF5', color: '#059669', text: '已发布' },
deprecated: { bg: '#FEF2F2', color: '#DC2626', text: '已弃用' },
draft: { bg: '#f6f5f4', color: '#615d59', text: '草稿' },
published: { bg: '#ecfdf5', color: '#1aae39', text: '已发布' },
deprecated: { bg: '#fef2f2', color: '#e5534b', text: '已弃用' },
};
export default function ProcessDefinitions() {
@@ -92,9 +92,9 @@ export default function ProcessDefinitions() {
key: 'key',
render: (v: string) => (
<Tag style={{
background: isDark ? '#1E293B' : '#F1F5F9',
background: isDark ? '#1E293B' : '#f6f5f4',
border: 'none',
color: isDark ? '#94A3B8' : '#64748B',
color: isDark ? '#a39e98' : '#615d59',
fontFamily: 'monospace',
fontSize: 12,
}}>
@@ -110,7 +110,7 @@ export default function ProcessDefinitions() {
key: 'status',
width: 100,
render: (s: string) => {
const info = statusColors[s] || { bg: '#F1F5F9', color: '#64748B', text: s };
const info = statusColors[s] || { bg: '#f6f5f4', color: '#615d59', text: s };
return (
<Tag style={{
background: info.bg,
@@ -152,7 +152,7 @@ export default function ProcessDefinitions() {
alignItems: 'center',
marginBottom: 16,
}}>
<span style={{ fontSize: 13, color: isDark ? '#64748B' : '#94A3B8' }}>
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98' }}>
{total}
</span>
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
@@ -163,7 +163,7 @@ export default function ProcessDefinitions() {
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
border: `1px solid ${isDark ? '#1E293B' : '#f6f5f4'}`,
overflow: 'hidden',
}}>
<Table