feat(web): 从 Notion 风格切换到 Pinterest 设计系统
- 替换 DESIGN.md 为 Pinterest 设计规格(暖色调、红色主题、大圆角) - 更新 CSS 变量:主色 #0075de→#e60023, 圆角 4px→16px, 背景 #f6f5f4→#f6f6f3 - 更新 Ant Design 主题令牌:更大圆角、Pinterest 色板、更大触控目标 - 批量更新 24 个页面/组件文件中的硬编码颜色值 - 暗色模式同步适配 Pinterest 暖色调暗色方案
This commit is contained in:
478
DESIGN.md
478
DESIGN.md
@@ -1,373 +1,263 @@
|
|||||||
# Design System: Notion
|
# Design System Inspired by Pinterest
|
||||||
|
|
||||||
## 1. Visual Theme & Atmosphere
|
## 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.
|
Pinterest's website is a warm, inspiration-driven canvas that treats visual discovery like a lifestyle magazine. The design operates on a soft, slightly warm white background with Pinterest Red (`#e60023`) as the singular, bold brand accent. Unlike the cool blues of most tech platforms, Pinterest's neutral scale has a distinctly warm undertone — grays lean toward olive/sand (`#91918c`, `#62625b`, `#e5e5e0`) rather than cool steel, creating a cozy, craft-like atmosphere that invites browsing.
|
||||||
|
|
||||||
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.
|
The typography uses Pin Sans — a custom proprietary font with a broad fallback stack including Japanese fonts, reflecting Pinterest's global reach. At display scale (70px, weight 600), Pin Sans creates large, inviting headlines. At smaller sizes, the system is compact: buttons at 12px, captions at 12–14px. The CSS variable naming system (`--comp-*`, `--sema-*`, `--base-*`) reveals a sophisticated three-tier design token architecture: component-level, semantic-level, and base-level tokens.
|
||||||
|
|
||||||
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.
|
What distinguishes Pinterest is its generous border-radius system (12px–40px, plus 50% for circles) and warm-tinted button backgrounds. The secondary button (`#e5e5e0`) has a distinctly warm, sand-like tone rather than cold gray. The primary red button uses 16px radius — rounded but not pill-shaped. Combined with warm badge backgrounds (`hsla(60,20%,98%,.5)` — a subtle yellow-warm wash) and photography-dominant layouts, the result is a design that feels handcrafted and personal, not corporate and sterile.
|
||||||
|
|
||||||
**Key Characteristics:**
|
**Key Characteristics:**
|
||||||
- NotionInter (modified Inter) with negative letter-spacing at display sizes (-2.125px at 64px)
|
- Warm white canvas with olive/sand-toned neutrals — cozy, not clinical
|
||||||
- Warm neutral palette: grays carry yellow-brown undertones (`#f6f5f4` warm white, `#31302e` warm dark)
|
- Pinterest Red (`#e60023`) as singular bold accent — never subtle, always confident
|
||||||
- Near-black text via `rgba(0,0,0,0.95)` -- not pure black, creating micro-warmth
|
- Pin Sans custom font with global fallback stack (including CJK)
|
||||||
- Ultra-thin borders: `1px solid rgba(0,0,0,0.1)` throughout -- whisper-weight division
|
- Three-tier token architecture: `--comp-*` / `--sema-*` / `--base-*`
|
||||||
- Multi-layer shadow stacks with sub-0.05 opacity for barely-there depth
|
- Warm secondary surfaces: sand gray (`#e5e5e0`), warm badge (`hsla(60,20%,98%,.5)`)
|
||||||
- Notion Blue (`#0075de`) as the singular accent color for CTAs and interactive elements
|
- Generous border-radius: 16px standard, up to 40px for large containers
|
||||||
- Pill badges (9999px radius) with tinted blue backgrounds for status indicators
|
- Photography-first content — pins/images are the primary visual element
|
||||||
- 8px base spacing unit with an organic, non-rigid scale
|
- Dark near-purple text (`#211922`) — warm, with a hint of plum
|
||||||
|
|
||||||
## 2. Color Palette & Roles
|
## 2. Color Palette & Roles
|
||||||
|
|
||||||
### Primary
|
### Primary Brand
|
||||||
- **Notion Black** (`rgba(0,0,0,0.95)` / `#000000f2`): Primary text, headings, body copy. The 95% opacity softens pure black without sacrificing readability.
|
- **Pinterest Red** (`#e60023`): Primary CTA, brand accent — bold, confident red
|
||||||
- **Pure White** (`#ffffff`): Page background, card surfaces, button text on blue.
|
- **Green 700** (`#103c25`): `--base-color-green-700`, success/nature accent
|
||||||
- **Notion Blue** (`#0075de`): Primary CTA, link color, interactive accent -- the only saturated color in the core UI chrome.
|
- **Green 700 Hover** (`#0b2819`): `--base-color-hover-green-700`, pressed green
|
||||||
|
|
||||||
### Brand Secondary
|
### Text
|
||||||
- **Deep Navy** (`#213183`): Secondary brand color, used sparingly for emphasis and dark feature sections.
|
- **Plum Black** (`#211922`): Primary text — warm near-black with plum undertone
|
||||||
- **Active Blue** (`#005bab`): Button active/pressed state -- darker variant of Notion Blue.
|
- **Black** (`#000000`): Secondary text, button text
|
||||||
|
- **Olive Gray** (`#62625b`): Secondary descriptions, muted text
|
||||||
### Warm Neutral Scale
|
- **Warm Silver** (`#91918c`): `--comp-button-color-text-transparent-disabled`, disabled text, input borders
|
||||||
- **Warm White** (`#f6f5f4`): Background surface tint, section alternation, subtle card fill. The yellow undertone is key.
|
- **White** (`#ffffff`): Text on dark/colored surfaces
|
||||||
- **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
|
### Interactive
|
||||||
- **Link Blue** (`#0075de`): Primary link color with underline-on-hover.
|
- **Focus Blue** (`#435ee5`): `--comp-button-color-border-focus-outer-transparent`, focus rings
|
||||||
- **Link Light Blue** (`#62aef0`): Lighter link variant for dark backgrounds.
|
- **Performance Purple** (`#6845ab`): `--sema-color-hover-icon-performance-plus`, performance features
|
||||||
- **Focus Blue** (`#097fe8`): Focus ring on interactive elements.
|
- **Recommendation Purple** (`#7e238b`): `--sema-color-hover-text-recommendation`, AI recommendation
|
||||||
- **Badge Blue Bg** (`#f2f9ff`): Pill badge background, tinted blue surface.
|
- **Link Blue** (`#2b48d4`): Link text color
|
||||||
- **Badge Blue Text** (`#097fe8`): Pill badge text, darker blue for readability.
|
- **Facebook Blue** (`#0866ff`): `--facebook-background-color`, social login
|
||||||
|
- **Pressed Blue** (`#617bff`): `--base-color-pressed-blue-200`, pressed state
|
||||||
|
|
||||||
### Shadows & Depth
|
### Surface & Border
|
||||||
- **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.
|
- **Sand Gray** (`#e5e5e0`): Secondary button background — warm, craft-like
|
||||||
- **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.
|
- **Warm Light** (`#e0e0d9`): Circular button backgrounds, badges
|
||||||
- **Whisper Border** (`1px solid rgba(0,0,0,0.1)`): Standard division border -- cards, dividers, sections.
|
- **Warm Wash** (`hsla(60, 20%, 98%, 0.5)`): `--comp-badge-color-background-wash-light`, subtle warm badge bg
|
||||||
|
- **Fog** (`#f6f6f3`): Light surface (at 50% opacity)
|
||||||
|
- **Border Disabled** (`#c8c8c1`): `--sema-color-border-disabled`, disabled borders
|
||||||
|
- **Hover Gray** (`#bcbcb3`): `--base-color-hover-grayscale-150`, hover border
|
||||||
|
- **Dark Surface** (`#33332e`): Dark section backgrounds
|
||||||
|
|
||||||
|
### Semantic
|
||||||
|
- **Error Red** (`#9e0a0a`): Checkbox/form error states
|
||||||
|
|
||||||
## 3. Typography Rules
|
## 3. Typography Rules
|
||||||
|
|
||||||
### Font Family
|
### Font Family
|
||||||
- **Primary**: `NotionInter`, with fallbacks: `Inter, -apple-system, system-ui, Segoe UI, Helvetica, Apple Color Emoji, Arial, Segoe UI Emoji, Segoe UI Symbol`
|
- **Primary**: `Pin Sans`, fallbacks: `-apple-system, system-ui, Segoe UI, Roboto, Oxygen-Sans, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, Helvetica, ヒラギノ角ゴ Pro W3, メイリオ, Meiryo, MS Pゴシック, Arial`
|
||||||
- **OpenType Features**: `"lnum"` (lining numerals) and `"locl"` (localized forms) enabled on display and heading text.
|
|
||||||
|
|
||||||
### Hierarchy
|
### Hierarchy
|
||||||
|
|
||||||
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|
| 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 Hero | Pin Sans | 70px (4.38rem) | 600 | normal | normal | Maximum impact |
|
||||||
| Display Secondary | NotionInter | 54px (3.38rem) | 700 | 1.04 (tight) | -1.875px | Secondary hero, feature headlines |
|
| Section Heading | Pin Sans | 28px (1.75rem) | 700 | normal | -1.2px | Negative tracking |
|
||||||
| Section Heading | NotionInter | 48px (3.00rem) | 700 | 1.00 (tight) | -1.5px | Feature section titles, with `"lnum"` |
|
| Body | Pin Sans | 16px (1.00rem) | 400 | 1.40 | normal | Standard reading |
|
||||||
| Sub-heading Large | NotionInter | 40px (2.50rem) | 700 | 1.50 | normal | Card headings, feature sub-sections |
|
| Caption Bold | Pin Sans | 14px (0.88rem) | 700 | normal | normal | Strong metadata |
|
||||||
| Sub-heading | NotionInter | 26px (1.63rem) | 700 | 1.23 (tight) | -0.625px | Section sub-titles, content headers |
|
| Caption | Pin Sans | 12px (0.75rem) | 400–500 | 1.50 | normal | Small text, tags |
|
||||||
| Card Title | NotionInter | 22px (1.38rem) | 700 | 1.27 (tight) | -0.25px | Feature cards, list titles |
|
| Button | Pin Sans | 12px (0.75rem) | 400 | normal | normal | Button labels |
|
||||||
| 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
|
### 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.
|
- **Compact type scale**: The range is 12px–70px with a dramatic jump — most functional text is 12–16px, creating a dense, app-like information hierarchy.
|
||||||
- **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 weight distribution**: 600–700 for headings, 400–500 for body. No ultra-light weights — the type always feels substantial.
|
||||||
- **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.
|
- **Negative tracking on headings**: -1.2px on 28px headings creates cozy, intimate section titles.
|
||||||
- **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.
|
- **Single font family**: Pin Sans handles everything — no secondary display or monospace font detected.
|
||||||
|
|
||||||
## 4. Component Stylings
|
## 4. Component Stylings
|
||||||
|
|
||||||
### Buttons
|
### Buttons
|
||||||
|
|
||||||
**Primary Blue**
|
**Primary Red**
|
||||||
- Background: `#0075de` (Notion Blue)
|
- Background: `#e60023` (Pinterest Red)
|
||||||
- Text: `#ffffff`
|
- Text: `#000000` (black — unusual choice for contrast on red)
|
||||||
- Padding: 8px 16px
|
- Padding: 6px 14px
|
||||||
- Radius: 4px (subtle)
|
- Radius: 16px (generously rounded, not pill)
|
||||||
- Border: `1px solid transparent`
|
- Border: `2px solid rgba(255, 255, 255, 0)` (transparent)
|
||||||
- Hover: background darkens to `#005bab`
|
- Focus: semantic border + outline via CSS variables
|
||||||
- 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**
|
**Secondary Sand**
|
||||||
- Background: `rgba(0,0,0,0.05)` (translucent warm gray)
|
- Background: `#e5e5e0` (warm sand gray)
|
||||||
- Text: `#000000` (near-black)
|
- Text: `#000000`
|
||||||
- Padding: 8px 16px
|
- Padding: 6px 14px
|
||||||
- Radius: 4px
|
- Radius: 16px
|
||||||
- Hover: text color shifts, scale(1.05)
|
- Focus: same semantic border system
|
||||||
- Active: scale(0.9) transform
|
|
||||||
- Use: Secondary actions, form submissions
|
|
||||||
|
|
||||||
**Ghost / Link Button**
|
**Circular Action**
|
||||||
|
- Background: `#e0e0d9` (warm light)
|
||||||
|
- Text: `#211922` (plum black)
|
||||||
|
- Radius: 50% (circle)
|
||||||
|
- Use: Pin actions, navigation controls
|
||||||
|
|
||||||
|
**Ghost / Transparent**
|
||||||
- Background: transparent
|
- Background: transparent
|
||||||
- Text: `rgba(0,0,0,0.95)`
|
- Text: `#000000`
|
||||||
- Decoration: underline on hover
|
- No border
|
||||||
- Use: Tertiary actions, inline links
|
- Use: Tertiary actions
|
||||||
|
|
||||||
**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
|
### Cards & Containers
|
||||||
- Background: `#ffffff`
|
- Photography-first pin cards with generous radius (12px–20px)
|
||||||
- Border: `1px solid rgba(0,0,0,0.1)` (whisper border)
|
- No traditional box-shadow on most cards
|
||||||
- Radius: 12px (standard cards), 16px (featured/hero cards)
|
- White or warm fog backgrounds
|
||||||
- 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`
|
- 8px white thick border on some image containers
|
||||||
- Hover: subtle shadow intensification
|
|
||||||
- Image cards: 12px top radius, image fills top half
|
|
||||||
|
|
||||||
### Inputs & Forms
|
### Inputs
|
||||||
- Background: `#ffffff`
|
- Email input: white background, `1px solid #91918c` border, 16px radius, 11px 15px padding
|
||||||
- Text: `rgba(0,0,0,0.9)`
|
- Focus: semantic border + outline system via CSS variables
|
||||||
- Border: `1px solid #dddddd`
|
|
||||||
- Padding: 6px
|
|
||||||
- Radius: 4px
|
|
||||||
- Focus: blue outline ring
|
|
||||||
- Placeholder: warm gray `#a39e98`
|
|
||||||
|
|
||||||
### Navigation
|
### Navigation
|
||||||
- Clean horizontal nav on white, not sticky
|
- Clean header on white or warm background
|
||||||
- Brand logo left-aligned (33x34px icon + wordmark)
|
- Pinterest logo + search bar centered
|
||||||
- Links: NotionInter 15px weight 500-600, near-black text
|
- Pin Sans 16px for nav links
|
||||||
- Hover: color shift to `var(--color-link-primary-text-hover)`
|
- Pinterest Red accents for active states
|
||||||
- CTA: blue pill button ("Get Notion free") right-aligned
|
|
||||||
- Mobile: hamburger menu collapse
|
|
||||||
- Product dropdowns with multi-level categorized menus
|
|
||||||
|
|
||||||
### Image Treatment
|
### Image Treatment
|
||||||
- Product screenshots with `1px solid rgba(0,0,0,0.1)` border
|
- Pin-style masonry grid (signature Pinterest layout)
|
||||||
- Top-rounded images: `12px 12px 0px 0px` radius
|
- Rounded corners: 12px–20px on images
|
||||||
- Dashboard/workspace preview screenshots dominate feature sections
|
- Photography as primary content — every pin is an image
|
||||||
- Warm gradient backgrounds behind hero illustrations (decorative character illustrations)
|
- Thick white borders (8px) on featured image containers
|
||||||
|
|
||||||
### 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
|
## 5. Layout Principles
|
||||||
|
|
||||||
### Spacing System
|
### Spacing System
|
||||||
- Base unit: 8px
|
- Base unit: 8px
|
||||||
- Scale: 2px, 3px, 4px, 5px, 6px, 7px, 8px, 11px, 12px, 14px, 16px, 24px, 32px
|
- Scale: 4px, 6px, 7px, 8px, 10px, 11px, 12px, 16px, 18px, 20px, 22px, 24px, 32px, 80px, 100px
|
||||||
- Non-rigid organic scale with fractional values (5.6px, 6.4px) for micro-adjustments
|
- Large jumps: 32px → 80px → 100px for section spacing
|
||||||
|
|
||||||
### Grid & Container
|
### Grid & Container
|
||||||
- Max content width: approximately 1200px
|
- Masonry grid for pin content (signature layout)
|
||||||
- Hero: centered single-column with generous top padding (80-120px)
|
- Centered content sections with generous max-width
|
||||||
- Feature sections: 2-3 column grids for cards
|
- Full-width dark footer
|
||||||
- Full-width warm white (`#f6f5f4`) section backgrounds for alternation
|
- Search bar as primary navigation element
|
||||||
- Code/dashboard screenshots as contained with whisper border
|
|
||||||
|
|
||||||
### Whitespace Philosophy
|
### Whitespace Philosophy
|
||||||
- **Generous vertical rhythm**: 64-120px between major sections. Notion lets content breathe with vast vertical padding.
|
- **Inspiration density**: The masonry grid packs pins tightly — the content density IS the value proposition. Whitespace exists between sections, not within the grid.
|
||||||
- **Warm alternation**: White sections alternate with warm white (`#f6f5f4`) sections, creating gentle visual rhythm without harsh color breaks.
|
- **Breathing above, density below**: Hero/feature sections get generous padding; the pin grid is compact and immersive.
|
||||||
- **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
|
### Border Radius Scale
|
||||||
- Micro (4px): Buttons, inputs, functional interactive elements
|
- Standard (12px): Small cards, links
|
||||||
- Subtle (5px): Links, list items, menu items
|
- Button (16px): Buttons, inputs, medium cards
|
||||||
- Standard (8px): Small cards, containers, inline elements
|
- Comfortable (20px): Feature cards
|
||||||
- Comfortable (12px): Standard cards, feature containers, image tops
|
- Large (28px): Large containers
|
||||||
- Large (16px): Hero cards, featured content, promotional blocks
|
- Section (32px): Tab elements, large panels
|
||||||
- Full Pill (9999px): Badges, pills, status indicators
|
- Hero (40px): Hero containers, large feature blocks
|
||||||
- Circle (100%): Tab indicators, avatars
|
- Circle (50%): Action buttons, tab indicators
|
||||||
|
|
||||||
## 6. Depth & Elevation
|
## 6. Depth & Elevation
|
||||||
|
|
||||||
| Level | Treatment | Use |
|
| Level | Treatment | Use |
|
||||||
|-------|-----------|-----|
|
|-------|-----------|-----|
|
||||||
| Flat (Level 0) | No shadow, no border | Page background, text blocks |
|
| Flat (Level 0) | No shadow | Default — pins rely on content, not shadow |
|
||||||
| Whisper (Level 1) | `1px solid rgba(0,0,0,0.1)` | Standard borders, card outlines, dividers |
|
| Subtle (Level 1) | Minimal shadow (from tokens) | Elevated overlays, dropdowns |
|
||||||
| Soft Card (Level 2) | 4-layer shadow stack (max opacity 0.04) | Content cards, feature blocks |
|
| Focus (Accessibility) | `--sema-color-border-focus-outer-default` ring | Focus states |
|
||||||
| 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.
|
**Shadow Philosophy**: Pinterest uses minimal shadows. The masonry grid relies on content (photography) to create visual interest rather than elevation effects. Depth comes from the warmth of surface colors and the generous rounding of containers.
|
||||||
|
|
||||||
### Decorative Depth
|
## 7. Do's and Don'ts
|
||||||
- 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
|
### Do
|
||||||
|
- Use warm neutrals (`#e5e5e0`, `#e0e0d9`, `#91918c`) — the warm olive/sand tone is the identity
|
||||||
|
- Apply Pinterest Red (`#e60023`) only for primary CTAs — it's bold and singular
|
||||||
|
- Use Pin Sans exclusively — one font for everything
|
||||||
|
- Apply generous border-radius: 16px for buttons/inputs, 20px+ for cards
|
||||||
|
- Keep the masonry grid dense — content density is the value
|
||||||
|
- Use warm badge backgrounds (`hsla(60,20%,98%,.5)`) for subtle warm washes
|
||||||
|
- Use `#211922` (plum black) for primary text — it's warmer than pure black
|
||||||
|
|
||||||
|
### Don't
|
||||||
|
- Don't use cool gray neutrals — always warm/olive-toned
|
||||||
|
- Don't use pure black (`#000000`) as primary text — use plum black (`#211922`)
|
||||||
|
- Don't use pill-shaped buttons — 16px radius is rounded but not pill
|
||||||
|
- Don't add heavy shadows — Pinterest is flat by design, depth from content
|
||||||
|
- Don't use small border-radius (<12px) on cards — the generous rounding is core
|
||||||
|
- Don't introduce additional brand colors — red + warm neutrals is the complete palette
|
||||||
|
- Don't use thin font weights — Pin Sans at 400 minimum
|
||||||
|
|
||||||
|
## 8. Responsive Behavior
|
||||||
|
|
||||||
### Breakpoints
|
### Breakpoints
|
||||||
|
|
||||||
| Name | Width | Key Changes |
|
| Name | Width | Key Changes |
|
||||||
|------|-------|-------------|
|
|------|-------|-------------|
|
||||||
| Mobile Small | <400px | Tight single column, minimal padding |
|
| Mobile | <576px | Single column, compact layout |
|
||||||
| Mobile | 400-600px | Standard mobile, stacked layout |
|
| Mobile Large | 576–768px | 2-column pin grid |
|
||||||
| Tablet Small | 600-768px | 2-column grids begin |
|
| Tablet | 768–890px | Expanded grid |
|
||||||
| Tablet | 768-1080px | Full card grids, expanded padding |
|
| Desktop Small | 890–1312px | Standard masonry grid |
|
||||||
| Desktop Small | 1080-1200px | Standard desktop layout |
|
| Desktop | 1312–1440px | Full layout |
|
||||||
| Desktop | 1200-1440px | Full layout, maximum content width |
|
| Large Desktop | 1440–1680px | Expanded grid columns |
|
||||||
| Large Desktop | >1440px | Centered, generous margins |
|
| Ultra-wide | >1680px | Maximum grid density |
|
||||||
|
|
||||||
### 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
|
### Collapsing Strategy
|
||||||
- Hero: 64px display -> scales to 40px -> 26px on mobile, maintains proportional letter-spacing
|
- Pin grid: 5+ columns → 3 → 2 → 1
|
||||||
- Navigation: horizontal links + blue CTA -> hamburger menu
|
- Navigation: search bar + icons → simplified mobile nav
|
||||||
- Feature cards: 3-column -> 2-column -> single column stacked
|
- Feature sections: side-by-side → stacked
|
||||||
- Product screenshots: maintain aspect ratio with responsive images
|
- Hero: 70px → scales down proportionally
|
||||||
- Trust bar logos: grid -> horizontal scroll on mobile
|
- Footer: dark multi-column → stacked
|
||||||
- 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
|
## 9. Agent Prompt Guide
|
||||||
|
|
||||||
### Quick Color Reference
|
### Quick Color Reference
|
||||||
- Primary CTA: Notion Blue (`#0075de`)
|
- Brand: Pinterest Red (`#e60023`)
|
||||||
- Background: Pure White (`#ffffff`)
|
- Background: White (`#ffffff`)
|
||||||
- Alt Background: Warm White (`#f6f5f4`)
|
- Text: Plum Black (`#211922`)
|
||||||
- Heading text: Near-Black (`rgba(0,0,0,0.95)`)
|
- Secondary text: Olive Gray (`#62625b`)
|
||||||
- Body text: Near-Black (`rgba(0,0,0,0.95)`)
|
- Button surface: Sand Gray (`#e5e5e0`)
|
||||||
- Secondary text: Warm Gray 500 (`#615d59`)
|
- Border: Warm Silver (`#91918c`)
|
||||||
- Muted text: Warm Gray 300 (`#a39e98`)
|
- Focus: Focus Blue (`#435ee5`)
|
||||||
- Border: `1px solid rgba(0,0,0,0.1)`
|
|
||||||
- Link: Notion Blue (`#0075de`)
|
|
||||||
- Focus ring: Focus Blue (`#097fe8`)
|
|
||||||
|
|
||||||
### Example Component Prompts
|
### 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)."
|
- "Create a hero: white background. Headline at 70px Pin Sans weight 600, plum black (#211922). Red CTA button (#e60023, 16px radius, 6px 14px padding). Secondary sand button (#e5e5e0, 16px radius)."
|
||||||
- "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."
|
- "Design a pin card: white background, 16px radius, no shadow. Photography fills top, 16px Pin Sans weight 400 description below in #62625b."
|
||||||
- "Build a pill badge: #f2f9ff background, #097fe8 text, 9999px radius, 4px 8px padding, 12px NotionInter weight 600, letter-spacing 0.125px."
|
- "Build a circular action button: #e0e0d9 background, 50% radius, #211922 icon."
|
||||||
- "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)."
|
- "Create an input field: white background, 1px solid #91918c, 16px radius, 11px 15px padding. Focus: blue outline via semantic tokens."
|
||||||
- "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."
|
- "Design the dark footer: #33332e background. Pinterest script logo in white. 12px Pin Sans links in #91918c."
|
||||||
|
|
||||||
### Iteration Guide
|
### Iteration Guide
|
||||||
1. Always use warm neutrals -- Notion's grays have yellow-brown undertones (#f6f5f4, #31302e, #615d59, #a39e98), never blue-gray
|
1. Warm neutrals everywhere — olive/sand grays, never cool steel
|
||||||
2. Letter-spacing scales with font size: -2.125px at 64px, -1.875px at 54px, -0.625px at 26px, normal at 16px
|
2. Pinterest Red for CTAs only — bold and singular
|
||||||
3. Four weights: 400 (read), 500 (interact), 600 (emphasize), 700 (announce)
|
3. 16px radius on buttons/inputs, 20px+ on cards — generous but not pill
|
||||||
4. Borders are whispers: 1px solid rgba(0,0,0,0.1) -- never heavier
|
4. Pin Sans is the only font — compact at 12px for UI, 70px for display
|
||||||
5. Shadows use 4-5 layers with individual opacity never exceeding 0.05
|
5. Photography carries the design — the UI stays warm and minimal
|
||||||
6. The warm white (#f6f5f4) section background is essential for visual rhythm
|
6. Plum black (#211922) for text — warmer than pure black
|
||||||
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
|
## 10. ERP Platform Adaptations
|
||||||
|
|
||||||
> This section adapts the Notion design system for our universal business ERP platform.
|
Pinterest's design system is adapted for an enterprise resource planning platform:
|
||||||
> We use Ant Design 5 components but override their theme tokens to match Notion's warm minimalism.
|
|
||||||
|
|
||||||
### Color Token Mapping (Ant Design Theme)
|
### Layout
|
||||||
- `colorPrimary`: `#0075de` (Notion Blue)
|
- Fixed sidebar navigation (240px wide, collapsible to 72px) replaces Pinterest's top nav
|
||||||
- `colorBgContainer`: `#ffffff`
|
- Sticky header (56px) with search, notifications, user menu
|
||||||
- `colorBgLayout`: `#f6f5f4` (Warm White)
|
- Content area uses CSS Grid for responsive multi-column dashboards
|
||||||
- `colorBorder`: `rgba(0,0,0,0.1)`
|
- Standard CRUD table/list views replace masonry grid for data management
|
||||||
- `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
|
### Color Adaptations
|
||||||
- Sidebar background: `#ffffff` (white, not dark)
|
- Pinterest Red (`#e60023`) for primary actions (Save, Create, Submit)
|
||||||
- Sidebar item text: `rgba(0,0,0,0.95)`
|
- Sand Gray (`#e5e5e0`) for secondary/outlined buttons
|
||||||
- Sidebar item hover: `#f6f5f4` background
|
- Focus Blue (`#435ee5`) for informational elements and links
|
||||||
- Sidebar item active: `#f2f9ff` background + `#0075de` left indicator
|
- Pinterest Green (`#103c25`) for success states
|
||||||
- Sidebar group headers: `#615d59` uppercase 11px weight 600 letter-spacing 0.5px
|
- Pinterest Error (`#9e0a0a`) for destructive/error states
|
||||||
- Sidebar collapsed: icon-only with tooltip
|
|
||||||
|
|
||||||
### Table/List Adaptation
|
### Component Adaptations
|
||||||
- Table header: `#f6f5f4` background, `#615d59` text, 13px weight 600
|
- **Tables**: Warm header bg (`#f6f6f3`), generous cell padding, red-tinted row hover
|
||||||
- Table row hover: `#fafaf9` (slightly warmer than pure white)
|
- **Forms**: 16px radius inputs, warm borders (`#91918c`), generous spacing
|
||||||
- Table row selected: `#f2f9ff` background
|
- **Cards**: 20px radius, minimal shadow, warm fog hover backgrounds
|
||||||
- Table border: `1px solid rgba(0,0,0,0.06)`
|
- **Sidebar**: Plum black active states, warm sand hover, 12px radius items
|
||||||
- Striped rows: optional, `#fafaf9` on even rows
|
- **Tags/Badges**: Warm pill shapes (8px radius), sand backgrounds
|
||||||
|
- **Modals**: 28px radius, warm shadows, generous padding
|
||||||
|
|
||||||
### Form Adaptation
|
### Dark Mode
|
||||||
- Input border: `1px solid rgba(0,0,0,0.15)`
|
- Background: `#1a1a18` (warm dark)
|
||||||
- Input focus: `#0075de` ring + soft shadow
|
- Container: `#2a2a28` (olive dark)
|
||||||
- Input background: `#ffffff`
|
- Elevated: `#33332e` (plum dark)
|
||||||
- Label: `rgba(0,0,0,0.95)` 14px weight 500
|
- Sidebar: `#211922` (plum black)
|
||||||
- Help text: `#a39e98` 12px weight 400
|
- Active accent: `#f05a5a` (warm light red)
|
||||||
- Error state: `#e5534b` border + text
|
- Text: Standard white/gray hierarchy with olive undertones
|
||||||
|
|
||||||
### 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`
|
|
||||||
|
|||||||
@@ -31,27 +31,27 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const themeConfig = {
|
const themeConfig = {
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: '#0075de',
|
colorPrimary: '#e60023',
|
||||||
colorSuccess: '#1aae39',
|
colorSuccess: '#103c25',
|
||||||
colorWarning: '#dd5b00',
|
colorWarning: '#b56e1a',
|
||||||
colorError: '#e5534b',
|
colorError: '#9e0a0a',
|
||||||
colorInfo: '#0075de',
|
colorInfo: '#435ee5',
|
||||||
colorBgLayout: '#f6f5f4',
|
colorBgLayout: '#f6f6f3',
|
||||||
colorBgContainer: '#ffffff',
|
colorBgContainer: '#ffffff',
|
||||||
colorBgElevated: '#ffffff',
|
colorBgElevated: '#ffffff',
|
||||||
colorBorder: 'rgba(0, 0, 0, 0.1)',
|
colorBorder: '#e5e5e0',
|
||||||
colorBorderSecondary: 'rgba(0, 0, 0, 0.06)',
|
colorBorderSecondary: '#e0e0d9',
|
||||||
borderRadius: 4,
|
borderRadius: 16,
|
||||||
borderRadiusLG: 8,
|
borderRadiusLG: 20,
|
||||||
borderRadiusSM: 2,
|
borderRadiusSM: 8,
|
||||||
fontFamily: "'Inter', -apple-system, system-ui, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', Helvetica, Arial, sans-serif",
|
fontFamily: "-apple-system, system-ui, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', Helvetica, Arial, sans-serif",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontSizeHeading4: 20,
|
fontSizeHeading4: 20,
|
||||||
controlHeight: 36,
|
controlHeight: 40,
|
||||||
controlHeightLG: 40,
|
controlHeightLG: 44,
|
||||||
controlHeightSM: 28,
|
controlHeightSM: 32,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
boxShadowSecondary: 'rgba(0,0,0,0.04) 0px 4px 18px, rgba(0,0,0,0.027) 0px 2.025px 7.85px',
|
boxShadowSecondary: '0 2px 8px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04)',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Button: {
|
Button: {
|
||||||
@@ -62,21 +62,21 @@ const themeConfig = {
|
|||||||
paddingLG: 20,
|
paddingLG: 20,
|
||||||
},
|
},
|
||||||
Table: {
|
Table: {
|
||||||
headerBg: '#fafaf9',
|
headerBg: '#fafaf8',
|
||||||
headerColor: '#615d59',
|
headerColor: '#62625b',
|
||||||
rowHoverBg: '#f2f9ff',
|
rowHoverBg: '#fef0f0',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
itemBorderRadius: 4,
|
itemBorderRadius: 12,
|
||||||
itemMarginInline: 8,
|
itemMarginInline: 8,
|
||||||
itemHeight: 36,
|
itemHeight: 40,
|
||||||
},
|
},
|
||||||
Modal: {
|
Modal: {
|
||||||
borderRadiusLG: 12,
|
borderRadiusLG: 28,
|
||||||
},
|
},
|
||||||
Tag: {
|
Tag: {
|
||||||
borderRadiusSM: 4,
|
borderRadiusSM: 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -85,20 +85,20 @@ const darkThemeConfig = {
|
|||||||
...themeConfig,
|
...themeConfig,
|
||||||
token: {
|
token: {
|
||||||
...themeConfig.token,
|
...themeConfig.token,
|
||||||
colorBgLayout: '#191918',
|
colorBgLayout: '#1a1a18',
|
||||||
colorBgContainer: '#232322',
|
colorBgContainer: '#2a2a28',
|
||||||
colorBgElevated: '#2a2a29',
|
colorBgElevated: '#33332e',
|
||||||
colorBorder: 'rgba(255, 255, 255, 0.08)',
|
colorBorder: 'rgba(255, 255, 255, 0.08)',
|
||||||
colorBorderSecondary: 'rgba(255, 255, 255, 0.05)',
|
colorBorderSecondary: 'rgba(255, 255, 255, 0.06)',
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
boxShadowSecondary: 'rgba(0,0,0,0.2) 0px 4px 18px, rgba(0,0,0,0.15) 0px 2px 8px',
|
boxShadowSecondary: '0 4px 16px rgba(0,0,0,0.2), 0 2px 6px rgba(0,0,0,0.15)',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
...themeConfig.components,
|
...themeConfig.components,
|
||||||
Table: {
|
Table: {
|
||||||
headerBg: '#2a2a29',
|
headerBg: '#33332e',
|
||||||
headerColor: '#a39e98',
|
headerColor: '#91918c',
|
||||||
rowHoverBg: '#2a2a29',
|
rowHoverBg: '#33332e',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function NotificationPanel() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
style={{ fontSize: 12, color: '#0075de' }}
|
style={{ fontSize: 12, color: '#e60023' }}
|
||||||
onClick={() => navigate('/messages')}
|
onClick={() => navigate('/messages')}
|
||||||
>
|
>
|
||||||
查看全部
|
查看全部
|
||||||
@@ -76,7 +76,7 @@ export default function NotificationPanel() {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'background 0.15s ease',
|
transition: 'background 0.15s ease',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
background: !item.is_read ? (isDark ? '#1e1e1d' : '#f2f9ff') : 'transparent',
|
background: !item.is_read ? (isDark ? '#211922' : '#fef0f0') : 'transparent',
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!item.is_read) {
|
if (!item.is_read) {
|
||||||
@@ -85,7 +85,7 @@ export default function NotificationPanel() {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
if (item.is_read) {
|
if (item.is_read) {
|
||||||
e.currentTarget.style.background = isDark ? '#1e1e1d' : '#fafaf9';
|
e.currentTarget.style.background = isDark ? '#211922' : '#fafaf8';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
@@ -109,7 +109,7 @@ export default function NotificationPanel() {
|
|||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: '#0075de',
|
background: '#e60023',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}} />
|
}} />
|
||||||
)}
|
)}
|
||||||
@@ -132,12 +132,12 @@ export default function NotificationPanel() {
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
borderTop: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
borderTop: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
}}>
|
}}>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
onClick={() => navigate('/messages')}
|
onClick={() => navigate('/messages')}
|
||||||
style={{ fontSize: 13, color: '#0075de', fontWeight: 500 }}
|
style={{ fontSize: 13, color: '#e60023', fontWeight: 500 }}
|
||||||
>
|
>
|
||||||
查看全部消息
|
查看全部消息
|
||||||
</Button>
|
</Button>
|
||||||
@@ -166,7 +166,7 @@ export default function NotificationPanel() {
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.background = isDark ? '#1e1e1d' : '#f6f5f4';
|
e.currentTarget.style.background = isDark ? '#211922' : '#f6f6f3';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.background = 'transparent';
|
e.currentTarget.style.background = 'transparent';
|
||||||
@@ -175,7 +175,7 @@ export default function NotificationPanel() {
|
|||||||
<Badge count={unreadCount} size="small" offset={[4, -4]}>
|
<Badge count={unreadCount} size="small" offset={[4, -4]}>
|
||||||
<BellOutlined style={{
|
<BellOutlined style={{
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
}} />
|
}} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,63 +2,63 @@
|
|||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
* ERP Platform — Design System Tokens & Global Styles
|
* ERP Platform — Design System Tokens & Global Styles
|
||||||
* Inspired by Notion: warm minimalism, whisper borders, soft elevation
|
* Inspired by Pinterest: warm discovery, red accent, generous radius
|
||||||
* ==================================================================== */
|
* ==================================================================== */
|
||||||
|
|
||||||
/* --- Design Tokens (CSS Custom Properties) --- */
|
/* --- Design Tokens (CSS Custom Properties) --- */
|
||||||
:root {
|
:root {
|
||||||
/* Primary Palette — Notion Blue */
|
/* Primary Palette — Pinterest Red */
|
||||||
--erp-primary: #0075de;
|
--erp-primary: #e60023;
|
||||||
--erp-primary-hover: #005bab;
|
--erp-primary-hover: #ad081b;
|
||||||
--erp-primary-active: #004a8c;
|
--erp-primary-active: #9e0a0a;
|
||||||
--erp-primary-light: #f2f9ff;
|
--erp-primary-light: #fef0f0;
|
||||||
--erp-primary-light-hover: #e4f1ff;
|
--erp-primary-light-hover: #fddbdb;
|
||||||
--erp-primary-bg-subtle: #f2f9ff;
|
--erp-primary-bg-subtle: #fef0f0;
|
||||||
|
|
||||||
/* Semantic Colors — Notion warm tones */
|
/* Semantic Colors — Pinterest warm tones */
|
||||||
--erp-success: #1aae39;
|
--erp-success: #103c25;
|
||||||
--erp-success-bg: #ecfdf5;
|
--erp-success-bg: #ecfdf5;
|
||||||
--erp-warning: #dd5b00;
|
--erp-warning: #b56e1a;
|
||||||
--erp-warning-bg: #fff7ed;
|
--erp-warning-bg: #fff7ed;
|
||||||
--erp-error: #e5534b;
|
--erp-error: #9e0a0a;
|
||||||
--erp-error-bg: #fef2f2;
|
--erp-error-bg: #fef2f2;
|
||||||
--erp-info: #0075de;
|
--erp-info: #435ee5;
|
||||||
--erp-info-bg: #f2f9ff;
|
--erp-info-bg: #eef1fd;
|
||||||
|
|
||||||
/* Neutral Palette — Warm neutrals with yellow-brown undertones */
|
/* Neutral Palette — Warm neutrals with olive/sand undertones */
|
||||||
--erp-bg-page: #f6f5f4;
|
--erp-bg-page: #f6f6f3;
|
||||||
--erp-bg-container: #ffffff;
|
--erp-bg-container: #ffffff;
|
||||||
--erp-bg-elevated: #ffffff;
|
--erp-bg-elevated: #ffffff;
|
||||||
--erp-bg-spotlight: #fafaf9;
|
--erp-bg-spotlight: #fafaf8;
|
||||||
--erp-bg-sidebar: #ffffff;
|
--erp-bg-sidebar: #ffffff;
|
||||||
--erp-bg-sidebar-hover: #f6f5f4;
|
--erp-bg-sidebar-hover: #f6f6f3;
|
||||||
--erp-bg-sidebar-active: #f2f9ff;
|
--erp-bg-sidebar-active: #fef0f0;
|
||||||
|
|
||||||
/* Text Colors — Warm near-black */
|
/* Text Colors — Warm near-black */
|
||||||
--erp-text-primary: rgba(0, 0, 0, 0.95);
|
--erp-text-primary: #211922;
|
||||||
--erp-text-secondary: #615d59;
|
--erp-text-secondary: #62625b;
|
||||||
--erp-text-tertiary: #a39e98;
|
--erp-text-tertiary: #91918c;
|
||||||
--erp-text-inverse: #ffffff;
|
--erp-text-inverse: #ffffff;
|
||||||
--erp-text-sidebar: #615d59;
|
--erp-text-sidebar: #62625b;
|
||||||
--erp-text-sidebar-active: #0075de;
|
--erp-text-sidebar-active: #e60023;
|
||||||
|
|
||||||
/* Border Colors — Whisper borders */
|
/* Border Colors — Whisper borders */
|
||||||
--erp-border: rgba(0, 0, 0, 0.1);
|
--erp-border: #e5e5e0;
|
||||||
--erp-border-light: rgba(0, 0, 0, 0.06);
|
--erp-border-light: #e0e0d9;
|
||||||
--erp-border-dark: rgba(0, 0, 0, 0.15);
|
--erp-border-dark: #c8c8c1;
|
||||||
|
|
||||||
/* Shadows — Multi-layer, ultra-subtle */
|
/* 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-xs: 0 1px 2px rgba(0,0,0,0.03);
|
||||||
--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-sm: 0 2px 8px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04);
|
||||||
--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-md: 0 4px 16px rgba(0,0,0,0.07), 0 2px 6px rgba(0,0,0,0.04);
|
||||||
--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-lg: 0 8px 24px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.05);
|
||||||
--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;
|
--erp-shadow-xl: 0 12px 36px rgba(0,0,0,0.1), 0 6px 18px rgba(0,0,0,0.06);
|
||||||
|
|
||||||
/* Radius — Notion subtle roundness */
|
/* Radius — Notion subtle roundness */
|
||||||
--erp-radius-sm: 2px;
|
--erp-radius-sm: 8px;
|
||||||
--erp-radius-md: 4px;
|
--erp-radius-md: 16px;
|
||||||
--erp-radius-lg: 8px;
|
--erp-radius-lg: 20px;
|
||||||
--erp-radius-xl: 12px;
|
--erp-radius-xl: 28px;
|
||||||
|
|
||||||
/* Spacing — 8px base unit */
|
/* Spacing — 8px base unit */
|
||||||
--erp-space-xs: 4px;
|
--erp-space-xs: 4px;
|
||||||
@@ -87,9 +87,9 @@
|
|||||||
--erp-transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
--erp-transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
/* Trend Colors — Warm */
|
/* Trend Colors — Warm */
|
||||||
--erp-trend-up: #1aae39;
|
--erp-trend-up: #103c25;
|
||||||
--erp-trend-down: #e5534b;
|
--erp-trend-down: #9e0a0a;
|
||||||
--erp-trend-neutral: #615d59;
|
--erp-trend-neutral: #62625b;
|
||||||
|
|
||||||
/* Line Height */
|
/* Line Height */
|
||||||
--erp-line-height-tight: 1.25;
|
--erp-line-height-tight: 1.25;
|
||||||
@@ -104,22 +104,22 @@
|
|||||||
|
|
||||||
/* --- Dark Mode Tokens — Warm dark, Notion-inspired --- */
|
/* --- Dark Mode Tokens — Warm dark, Notion-inspired --- */
|
||||||
[data-theme='dark'] {
|
[data-theme='dark'] {
|
||||||
--erp-primary-light: rgba(0, 117, 222, 0.15);
|
--erp-primary-light: rgba(230, 0, 35, 0.15);
|
||||||
--erp-primary-light-hover: rgba(0, 117, 222, 0.22);
|
--erp-primary-light-hover: rgba(230, 0, 35, 0.22);
|
||||||
--erp-primary-bg-subtle: rgba(0, 117, 222, 0.1);
|
--erp-primary-bg-subtle: rgba(230, 0, 35, 0.1);
|
||||||
|
|
||||||
--erp-bg-page: #191918;
|
--erp-bg-page: #1a1a18;
|
||||||
--erp-bg-container: #232322;
|
--erp-bg-container: #2a2a28;
|
||||||
--erp-bg-elevated: #2a2a29;
|
--erp-bg-elevated: #33332e;
|
||||||
--erp-bg-spotlight: #2a2a29;
|
--erp-bg-spotlight: #33332e;
|
||||||
--erp-bg-sidebar: #1e1e1d;
|
--erp-bg-sidebar: #211922;
|
||||||
--erp-bg-sidebar-hover: #2a2a29;
|
--erp-bg-sidebar-hover: #33332e;
|
||||||
|
|
||||||
--erp-text-primary: rgba(255, 255, 255, 0.95);
|
--erp-text-primary: rgba(255, 255, 255, 0.95);
|
||||||
--erp-text-secondary: #a39e98;
|
--erp-text-secondary: #91918c;
|
||||||
--erp-text-tertiary: #6d6862;
|
--erp-text-tertiary: #62625b;
|
||||||
--erp-text-sidebar: #a39e98;
|
--erp-text-sidebar: #91918c;
|
||||||
--erp-text-sidebar-active: #62aef0;
|
--erp-text-sidebar-active: #f05a5a;
|
||||||
|
|
||||||
--erp-border: rgba(255, 255, 255, 0.08);
|
--erp-border: rgba(255, 255, 255, 0.08);
|
||||||
--erp-border-light: rgba(255, 255, 255, 0.05);
|
--erp-border-light: rgba(255, 255, 255, 0.05);
|
||||||
@@ -130,20 +130,20 @@
|
|||||||
--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-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-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-up: #3db377;
|
||||||
--erp-trend-down: #e5534b;
|
--erp-trend-down: #9e0a0a;
|
||||||
--erp-trend-neutral: #a39e98;
|
--erp-trend-neutral: #91918c;
|
||||||
|
|
||||||
--erp-success-bg: rgba(26, 174, 57, 0.15);
|
--erp-success-bg: rgba(16, 60, 37, 0.15);
|
||||||
--erp-warning-bg: rgba(221, 91, 0, 0.15);
|
--erp-warning-bg: rgba(181, 110, 26, 0.15);
|
||||||
--erp-error-bg: rgba(229, 83, 75, 0.15);
|
--erp-error-bg: rgba(158, 10, 10, 0.15);
|
||||||
--erp-info-bg: rgba(0, 117, 222, 0.15);
|
--erp-info-bg: rgba(67, 94, 229, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-stat-card-trend-up { color: #2a9d99; }
|
[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-down { color: #e5534b; }
|
||||||
[data-theme='dark'] .erp-stat-card-trend-neutral { color: #a39e98; }
|
[data-theme='dark'] .erp-stat-card-trend-neutral { color: #91918c; }
|
||||||
[data-theme='dark'] .erp-stat-card-trend-label { color: #a39e98; }
|
[data-theme='dark'] .erp-stat-card-trend-label { color: #91918c; }
|
||||||
|
|
||||||
/* --- Global Reset & Base --- */
|
/* --- Global Reset & Base --- */
|
||||||
body {
|
body {
|
||||||
@@ -182,7 +182,7 @@ body {
|
|||||||
|
|
||||||
/* --- Selection --- */
|
/* --- Selection --- */
|
||||||
::selection {
|
::selection {
|
||||||
background-color: rgba(0, 117, 222, 0.15);
|
background-color: rgba(230, 0, 35, 0.15);
|
||||||
color: var(--erp-text-primary);
|
color: var(--erp-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ body {
|
|||||||
|
|
||||||
/* --- Button — Subtle 4px radius, no heavy shadow --- */
|
/* --- Button — Subtle 4px radius, no heavy shadow --- */
|
||||||
.ant-btn-primary {
|
.ant-btn-primary {
|
||||||
border-radius: 4px !important;
|
border-radius: 16px !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
transition: all var(--erp-transition-fast) !important;
|
transition: all var(--erp-transition-fast) !important;
|
||||||
@@ -283,7 +283,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-default {
|
.ant-btn-default {
|
||||||
border-radius: 4px !important;
|
border-radius: 16px !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ body {
|
|||||||
.ant-input-affix-wrapper,
|
.ant-input-affix-wrapper,
|
||||||
.ant-select-selector,
|
.ant-select-selector,
|
||||||
.ant-picker {
|
.ant-picker {
|
||||||
border-radius: 4px !important;
|
border-radius: 16px !important;
|
||||||
transition: all var(--erp-transition-fast) !important;
|
transition: all var(--erp-transition-fast) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ body {
|
|||||||
.ant-select-focused .ant-select-selector,
|
.ant-select-focused .ant-select-selector,
|
||||||
.ant-picker-focused {
|
.ant-picker-focused {
|
||||||
border-color: var(--erp-primary) !important;
|
border-color: var(--erp-primary) !important;
|
||||||
box-shadow: 0 0 0 2px rgba(0, 117, 222, 0.12) !important;
|
box-shadow: 0 0 0 2px rgba(230, 0, 35, 0.12) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Modal --- */
|
/* --- Modal --- */
|
||||||
@@ -436,12 +436,12 @@ body {
|
|||||||
border-radius: var(--erp-radius-lg) var(--erp-radius-lg) 0 0;
|
border-radius: var(--erp-radius-lg) var(--erp-radius-lg) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-gradient-card.indigo::before { background: linear-gradient(90deg, #0075de, #62aef0); }
|
.erp-gradient-card.indigo::before { background: linear-gradient(90deg, #e60023, #f05a5a); }
|
||||||
.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #1aae39, #4ade80); }
|
.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #103c25, #3db377); }
|
||||||
.erp-gradient-card.amber::before { background: linear-gradient(90deg, #dd5b00, #fbbf24); }
|
.erp-gradient-card.amber::before { background: linear-gradient(90deg, #b56e1a, #fbbf24); }
|
||||||
.erp-gradient-card.rose::before { background: linear-gradient(90deg, #e5534b, #f87171); }
|
.erp-gradient-card.rose::before { background: linear-gradient(90deg, #9e0a0a, #f05a5a); }
|
||||||
.erp-gradient-card.sky::before { background: linear-gradient(90deg, #0075de, #38bdf8); }
|
.erp-gradient-card.sky::before { background: linear-gradient(90deg, #435ee5, #8fa4f0); }
|
||||||
.erp-gradient-card.violet::before { background: linear-gradient(90deg, #391c57, #a78bfa); }
|
.erp-gradient-card.violet::before { background: linear-gradient(90deg, #6845ab, #a78bfa); }
|
||||||
|
|
||||||
/* --- Fade-in Animation --- */
|
/* --- Fade-in Animation --- */
|
||||||
@keyframes erp-fade-in {
|
@keyframes erp-fade-in {
|
||||||
@@ -475,7 +475,7 @@ body {
|
|||||||
*:focus-visible {
|
*:focus-visible {
|
||||||
outline: 2px solid var(--erp-primary);
|
outline: 2px solid var(--erp-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-item:focus-visible {
|
.erp-sidebar-item:focus-visible {
|
||||||
@@ -491,7 +491,7 @@ body {
|
|||||||
background: var(--erp-primary);
|
background: var(--erp-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 8px 24px;
|
padding: 8px 24px;
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 16px 16px;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -540,22 +540,22 @@ body {
|
|||||||
|
|
||||||
.erp-sidebar-menu .ant-menu-item {
|
.erp-sidebar-menu .ant-menu-item {
|
||||||
margin: 1px 8px !important;
|
margin: 1px 8px !important;
|
||||||
border-radius: 4px !important;
|
border-radius: 16px !important;
|
||||||
height: 36px !important;
|
height: 36px !important;
|
||||||
line-height: 36px !important;
|
line-height: 36px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-menu .ant-menu-item-selected {
|
.erp-sidebar-menu .ant-menu-item-selected {
|
||||||
background: #f2f9ff !important;
|
background: #fef0f0 !important;
|
||||||
color: #0075de !important;
|
color: #e60023 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-menu .ant-menu-item-selected .anticon {
|
.erp-sidebar-menu .ant-menu-item-selected .anticon {
|
||||||
color: #0075de !important;
|
color: #e60023 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-menu .ant-menu-item:not(.ant-menu-item-selected):hover {
|
.erp-sidebar-menu .ant-menu-item:not(.ant-menu-item-selected):hover {
|
||||||
background: #f6f5f4 !important;
|
background: #f6f6f3 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar group label */
|
/* Sidebar group label */
|
||||||
@@ -565,7 +565,7 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.8px;
|
letter-spacing: 0.8px;
|
||||||
color: #a39e98;
|
color: #91918c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
@@ -575,7 +575,7 @@ body {
|
|||||||
/* Sider — White sidebar, Notion style */
|
/* Sider — White sidebar, Notion style */
|
||||||
.erp-sider-dark {
|
.erp-sider-dark {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
border-right: 1px solid #e0e0d9 !important;
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -585,8 +585,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sider-dark {
|
[data-theme='dark'] .erp-sider-dark {
|
||||||
background: #1e1e1d !important;
|
background: #211922 !important;
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.05) !important;
|
border-right: 1px solid rgba(255, 255, 255, 0.06) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logo — Warm neutral, Notion style */
|
/* Logo — Warm neutral, Notion style */
|
||||||
@@ -595,12 +595,12 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
border-bottom: 1px solid #e0e0d9;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-logo {
|
[data-theme='dark'] .erp-sidebar-logo {
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-sider-collapsed .erp-sidebar-logo {
|
.ant-layout-sider-collapsed .erp-sidebar-logo {
|
||||||
@@ -611,8 +611,8 @@ body {
|
|||||||
.erp-sidebar-logo-icon {
|
.erp-sidebar-logo-icon {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: 4px;
|
border-radius: 16px;
|
||||||
background: #0075de;
|
background: #e60023;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -624,7 +624,7 @@ body {
|
|||||||
|
|
||||||
.erp-sidebar-logo-text {
|
.erp-sidebar-logo-text {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: rgba(0, 0, 0, 0.95);
|
color: #211922;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.3px;
|
letter-spacing: -0.3px;
|
||||||
@@ -642,9 +642,9 @@ body {
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 1px 8px;
|
margin: 1px 8px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border-radius: 4px;
|
border-radius: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #615d59;
|
color: #62625b;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
@@ -657,28 +657,28 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-item:hover:not(.erp-sidebar-item-active) {
|
.erp-sidebar-item:hover:not(.erp-sidebar-item-active) {
|
||||||
background: #f6f5f4;
|
background: #f6f6f3;
|
||||||
color: rgba(0, 0, 0, 0.95);
|
color: #211922;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-item {
|
[data-theme='dark'] .erp-sidebar-item {
|
||||||
color: #a39e98;
|
color: #91918c;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-item:hover:not(.erp-sidebar-item-active) {
|
[data-theme='dark'] .erp-sidebar-item:hover:not(.erp-sidebar-item-active) {
|
||||||
background: #2a2a29;
|
background: #33332e;
|
||||||
color: rgba(255, 255, 255, 0.95);
|
color: rgba(255, 255, 255, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-item-active {
|
.erp-sidebar-item-active {
|
||||||
background: #f2f9ff;
|
background: #fef0f0;
|
||||||
color: #0075de;
|
color: #e60023;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-item-active {
|
[data-theme='dark'] .erp-sidebar-item-active {
|
||||||
background: rgba(0, 117, 222, 0.15);
|
background: rgba(230, 0, 35, 0.15);
|
||||||
color: #62aef0;
|
color: #f05a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-item-icon {
|
.erp-sidebar-item-icon {
|
||||||
@@ -698,9 +698,9 @@ body {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
margin: 6px 8px 2px 8px;
|
margin: 6px 8px 2px 8px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border-radius: 4px;
|
border-radius: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #a39e98;
|
color: #91918c;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -710,25 +710,25 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-submenu-title:hover {
|
.erp-sidebar-submenu-title:hover {
|
||||||
background: #f6f5f4;
|
background: #f6f6f3;
|
||||||
color: #615d59;
|
color: #62625b;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-submenu-title {
|
[data-theme='dark'] .erp-sidebar-submenu-title {
|
||||||
color: #6d6862;
|
color: #62625b;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-submenu-title:hover {
|
[data-theme='dark'] .erp-sidebar-submenu-title:hover {
|
||||||
background: #2a2a29;
|
background: #33332e;
|
||||||
color: #a39e98;
|
color: #91918c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-submenu-title-active {
|
.erp-sidebar-submenu-title-active {
|
||||||
color: #0075de;
|
color: #e60023;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-submenu-title-active {
|
[data-theme='dark'] .erp-sidebar-submenu-title-active {
|
||||||
color: #62aef0;
|
color: #f05a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-submenu-arrow {
|
.erp-sidebar-submenu-arrow {
|
||||||
@@ -753,8 +753,8 @@ body {
|
|||||||
transition: margin-left 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: margin-left 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-main-layout-light { background: #f6f5f4; }
|
.erp-main-layout-light { background: #f6f6f3; }
|
||||||
.erp-main-layout-dark { background: #191918; }
|
.erp-main-layout-dark { background: #1a1a18; }
|
||||||
|
|
||||||
/* Header — Clean white, whisper border bottom */
|
/* Header — Clean white, whisper border bottom */
|
||||||
.erp-header {
|
.erp-header {
|
||||||
@@ -771,39 +771,39 @@ body {
|
|||||||
|
|
||||||
.erp-header-light {
|
.erp-header-light {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
border-bottom: 1px solid #e0e0d9;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-dark {
|
.erp-header-dark {
|
||||||
background: #232322 !important;
|
background: #2a2a28 !important;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-btn {
|
.erp-header-btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 4px;
|
border-radius: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
color: #615d59;
|
color: #62625b;
|
||||||
will-change: background;
|
will-change: background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-light .erp-header-btn { color: #615d59; }
|
.erp-header-light .erp-header-btn { color: #62625b; }
|
||||||
.erp-header-dark .erp-header-btn { color: #a39e98; }
|
.erp-header-dark .erp-header-btn { color: #91918c; }
|
||||||
.erp-header-btn:hover { background: #f6f5f4; }
|
.erp-header-btn:hover { background: #f6f6f3; }
|
||||||
.erp-header-dark .erp-header-btn:hover { background: #2a2a29; }
|
.erp-header-dark .erp-header-btn:hover { background: #33332e; }
|
||||||
|
|
||||||
.erp-header-title { font-size: 15px; font-weight: 600; }
|
.erp-header-title { font-size: 15px; font-weight: 600; }
|
||||||
.erp-text-light { color: rgba(0, 0, 0, 0.95); }
|
.erp-text-light { color: #211922; }
|
||||||
.erp-text-dark { color: rgba(255, 255, 255, 0.95); }
|
.erp-text-dark { color: rgba(255, 255, 255, 0.95); }
|
||||||
.erp-text-light-secondary { color: #615d59; }
|
.erp-text-light-secondary { color: #62625b; }
|
||||||
.erp-text-dark-secondary { color: #a39e98; }
|
.erp-text-dark-secondary { color: #91918c; }
|
||||||
|
|
||||||
.erp-header-divider { width: 1px; height: 24px; margin: 0 8px; }
|
.erp-header-divider { width: 1px; height: 24px; margin: 0 8px; }
|
||||||
.erp-header-divider-light { background: rgba(0, 0, 0, 0.06); }
|
.erp-header-divider-light { background: rgba(0, 0, 0, 0.06); }
|
||||||
@@ -820,8 +820,8 @@ body {
|
|||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-user:hover { background: #f6f5f4; }
|
.erp-header-user:hover { background: #f6f6f3; }
|
||||||
.erp-header-dark .erp-header-user:hover { background: #2a2a29; }
|
.erp-header-dark .erp-header-user:hover { background: #33332e; }
|
||||||
|
|
||||||
.erp-user-avatar {
|
.erp-user-avatar {
|
||||||
background: #0075de !important;
|
background: #0075de !important;
|
||||||
@@ -832,8 +832,8 @@ body {
|
|||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
.erp-footer { text-align: center; padding: 12px 24px !important; background: transparent !important; font-size: 12px; }
|
.erp-footer { text-align: center; padding: 12px 24px !important; background: transparent !important; font-size: 12px; }
|
||||||
.erp-footer-light { color: #a39e98; }
|
.erp-footer-light { color: #91918c; }
|
||||||
.erp-footer-dark { color: #6d6862; }
|
.erp-footer-dark { color: #62625b; }
|
||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
* Dashboard — Stat Cards & Quick Actions (replacing inline styles)
|
* Dashboard — Stat Cards & Quick Actions (replacing inline styles)
|
||||||
@@ -864,7 +864,7 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background: var(--card-gradient, linear-gradient(135deg, #0075de, #62aef0));
|
background: var(--card-gradient, linear-gradient(135deg, #e60023, #f05a5a));
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-stat-card-body {
|
.erp-stat-card-body {
|
||||||
@@ -895,7 +895,7 @@ body {
|
|||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-radius: var(--erp-radius-lg);
|
border-radius: var(--erp-radius-lg);
|
||||||
background: var(--card-icon-bg, rgba(0, 117, 222, 0.08));
|
background: var(--card-icon-bg, rgba(230, 0, 35, 0.08));
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -913,7 +913,7 @@ body {
|
|||||||
|
|
||||||
.erp-section-icon {
|
.erp-section-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #0075de;
|
color: #e60023;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-section-title {
|
.erp-section-title {
|
||||||
@@ -928,7 +928,7 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s ease, border-color 0.15s ease;
|
transition: background 0.15s ease, border-color 0.15s ease;
|
||||||
background: var(--erp-bg-spotlight);
|
background: var(--erp-bg-spotlight);
|
||||||
@@ -936,17 +936,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.erp-quick-action:hover {
|
.erp-quick-action:hover {
|
||||||
background: #f2f9ff;
|
background: #fef0f0;
|
||||||
border-color: var(--action-color, #0075de);
|
border-color: var(--action-color, #e60023);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-quick-action {
|
[data-theme='dark'] .erp-quick-action {
|
||||||
background: #191918;
|
background: #1a1a18;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-quick-action:hover {
|
[data-theme='dark'] .erp-quick-action:hover {
|
||||||
background: #2a2a29;
|
background: #33332e;
|
||||||
border-color: var(--action-color, #0075de);
|
border-color: var(--action-color, #e60023);
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-quick-action-icon {
|
.erp-quick-action-icon {
|
||||||
@@ -956,8 +956,8 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: color-mix(in srgb, var(--action-color, #0075de) 8%, transparent);
|
background: color-mix(in srgb, var(--action-color, #e60023) 8%, transparent);
|
||||||
color: var(--action-color, #0075de);
|
color: var(--action-color, #e60023);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -1008,12 +1008,12 @@ body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-stat-card-trend-up { color: #047857; }
|
.erp-stat-card-trend-up { color: #0b5030; }
|
||||||
.erp-stat-card-trend-down { color: #B91C1C; }
|
.erp-stat-card-trend-down { color: #9e0a0a; }
|
||||||
.erp-stat-card-trend-neutral { color: #64748B; }
|
.erp-stat-card-trend-neutral { color: #62625b; }
|
||||||
|
|
||||||
.erp-stat-card-trend-label {
|
.erp-stat-card-trend-label {
|
||||||
color: #64748B;
|
color: #62625b;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,8 +1046,8 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: color-mix(in srgb, var(--action-color, #0075de) 8%, transparent);
|
background: color-mix(in srgb, var(--action-color, #e60023) 8%, transparent);
|
||||||
color: var(--action-color, #0075de);
|
color: var(--action-color, #e60023);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: transform 0.15s ease;
|
transition: transform 0.15s ease;
|
||||||
@@ -1075,7 +1075,7 @@ body {
|
|||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border-radius: var(--erp-radius-md);
|
border-radius: var(--erp-radius-md);
|
||||||
background: var(--erp-bg-spotlight);
|
background: var(--erp-bg-spotlight);
|
||||||
border-left: 3px solid var(--task-color, #0075de);
|
border-left: 3px solid var(--task-color, #e60023);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
@@ -1092,8 +1092,8 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: color-mix(in srgb, var(--task-color, #0075de) 8%, transparent);
|
background: color-mix(in srgb, var(--task-color, #e60023) 8%, transparent);
|
||||||
color: var(--task-color, #0075de);
|
color: var(--task-color, #e60023);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -1115,25 +1115,25 @@ body {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
font-size: var(--erp-font-size-xs);
|
font-size: var(--erp-font-size-xs);
|
||||||
color: #a39e98;
|
color: #91918c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-task-priority {
|
.erp-task-priority {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1px 8px;
|
padding: 1px 8px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-task-priority-high { background: #fef2f2; color: #e5534b; }
|
.erp-task-priority-high { background: #fef2f2; color: #9e0a0a; }
|
||||||
.erp-task-priority-medium { background: #fff7ed; color: #dd5b00; }
|
.erp-task-priority-medium { background: #fff7ed; color: #b56e1a; }
|
||||||
.erp-task-priority-low { background: #ecfdf5; color: #1aae39; }
|
.erp-task-priority-low { background: #ecfdf5; color: #103c25; }
|
||||||
|
|
||||||
[data-theme='dark'] .erp-task-priority-high { background: rgba(229, 83, 75, 0.15); color: #e5534b; }
|
[data-theme='dark'] .erp-task-priority-high { background: rgba(158, 10, 10, 0.15); color: #f05a5a; }
|
||||||
[data-theme='dark'] .erp-task-priority-medium { background: rgba(221, 91, 0, 0.15); color: #dd5b00; }
|
[data-theme='dark'] .erp-task-priority-medium { background: rgba(181, 110, 26, 0.15); color: #d4852a; }
|
||||||
[data-theme='dark'] .erp-task-priority-low { background: rgba(26, 174, 57, 0.15); color: #1aae39; }
|
[data-theme='dark'] .erp-task-priority-low { background: rgba(16, 60, 37, 0.15); color: #3db377; }
|
||||||
|
|
||||||
/* Activity Timeline */
|
/* Activity Timeline */
|
||||||
.erp-activity-list {
|
.erp-activity-list {
|
||||||
@@ -1189,12 +1189,12 @@ body {
|
|||||||
|
|
||||||
.erp-activity-time {
|
.erp-activity-time {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #a39e98;
|
color: #91918c;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-activity-time {
|
[data-theme='dark'] .erp-activity-time {
|
||||||
color: #6d6862;
|
color: #62625b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Empty State */
|
/* Empty State */
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export default function Home() {
|
|||||||
title: '用户总数',
|
title: '用户总数',
|
||||||
value: stats.userCount,
|
value: stats.userCount,
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
gradient: 'linear-gradient(135deg, #0075de, #62aef0)',
|
gradient: 'linear-gradient(135deg, #e60023, #f05a5a)',
|
||||||
iconBg: 'rgba(79, 70, 229, 0.12)',
|
iconBg: 'rgba(79, 70, 229, 0.12)',
|
||||||
delay: 'erp-fade-in erp-fade-in-delay-1',
|
delay: 'erp-fade-in erp-fade-in-delay-1',
|
||||||
trend: { value: '+2', direction: 'up', label: '较上周' },
|
trend: { value: '+2', direction: 'up', label: '较上周' },
|
||||||
@@ -179,7 +179,7 @@ export default function Home() {
|
|||||||
title: '角色数量',
|
title: '角色数量',
|
||||||
value: stats.roleCount,
|
value: stats.roleCount,
|
||||||
icon: <SafetyCertificateOutlined />,
|
icon: <SafetyCertificateOutlined />,
|
||||||
gradient: 'linear-gradient(135deg, #1aae39, #10B981)',
|
gradient: 'linear-gradient(135deg, #103c25, #10B981)',
|
||||||
iconBg: 'rgba(5, 150, 105, 0.12)',
|
iconBg: 'rgba(5, 150, 105, 0.12)',
|
||||||
delay: 'erp-fade-in erp-fade-in-delay-2',
|
delay: 'erp-fade-in erp-fade-in-delay-2',
|
||||||
trend: { value: '+1', direction: 'up', label: '较上月' },
|
trend: { value: '+1', direction: 'up', label: '较上月' },
|
||||||
@@ -191,7 +191,7 @@ export default function Home() {
|
|||||||
title: '流程实例',
|
title: '流程实例',
|
||||||
value: stats.processInstanceCount,
|
value: stats.processInstanceCount,
|
||||||
icon: <FileTextOutlined />,
|
icon: <FileTextOutlined />,
|
||||||
gradient: 'linear-gradient(135deg, #dd5b00, #F59E0B)',
|
gradient: 'linear-gradient(135deg, #b56e1a, #F59E0B)',
|
||||||
iconBg: 'rgba(217, 119, 6, 0.12)',
|
iconBg: 'rgba(217, 119, 6, 0.12)',
|
||||||
delay: 'erp-fade-in erp-fade-in-delay-3',
|
delay: 'erp-fade-in erp-fade-in-delay-3',
|
||||||
trend: { value: '0', direction: 'neutral', label: '较昨日' },
|
trend: { value: '0', direction: 'neutral', label: '较昨日' },
|
||||||
@@ -213,18 +213,18 @@ export default function Home() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const quickActions = [
|
const quickActions = [
|
||||||
{ icon: <UserOutlined />, label: '用户管理', path: '/users', color: '#0075de' },
|
{ icon: <UserOutlined />, label: '用户管理', path: '/users', color: '#e60023' },
|
||||||
{ icon: <SafetyCertificateOutlined />, label: '权限管理', path: '/roles', color: '#1aae39' },
|
{ icon: <SafetyCertificateOutlined />, label: '权限管理', path: '/roles', color: '#103c25' },
|
||||||
{ icon: <ApartmentOutlined />, label: '组织架构', path: '/organizations', color: '#dd5b00' },
|
{ icon: <ApartmentOutlined />, label: '组织架构', path: '/organizations', color: '#b56e1a' },
|
||||||
{ icon: <PartitionOutlined />, label: '工作流', path: '/workflow', color: '#7C3AED' },
|
{ icon: <PartitionOutlined />, label: '工作流', path: '/workflow', color: '#7C3AED' },
|
||||||
{ icon: <BellOutlined />, label: '消息中心', path: '/messages', color: '#E11D48' },
|
{ icon: <BellOutlined />, label: '消息中心', path: '/messages', color: '#E11D48' },
|
||||||
{ icon: <SettingOutlined />, label: '系统设置', path: '/settings', color: '#615d59' },
|
{ icon: <SettingOutlined />, label: '系统设置', path: '/settings', color: '#62625b' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const pendingTasks: TaskItem[] = [
|
const pendingTasks: TaskItem[] = [
|
||||||
{ id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#e5534b', icon: <UserOutlined />, path: '/users' },
|
{ id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#9e0a0a', icon: <UserOutlined />, path: '/users' },
|
||||||
{ id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#dd5b00', icon: <PartitionOutlined />, path: '/workflow' },
|
{ id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#b56e1a', icon: <PartitionOutlined />, path: '/workflow' },
|
||||||
{ id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#1aae39', icon: <SafetyCertificateOutlined />, path: '/roles' },
|
{ id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#103c25', icon: <SafetyCertificateOutlined />, path: '/roles' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const recentActivities: ActivityItem[] = [
|
const recentActivities: ActivityItem[] = [
|
||||||
@@ -243,13 +243,13 @@ export default function Home() {
|
|||||||
<h2 style={{
|
<h2 style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: isDark ? '#f6f5f4' : 'rgba(0,0,0,0.95)',
|
color: isDark ? '#f6f6f3' : 'rgba(0,0,0,0.95)',
|
||||||
margin: '0 0 4px',
|
margin: '0 0 4px',
|
||||||
letterSpacing: '-0.5px',
|
letterSpacing: '-0.5px',
|
||||||
}}>
|
}}>
|
||||||
工作台
|
工作台
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ fontSize: 14, color: isDark ? '#a39e98' : '#615d59', margin: 0 }}>
|
<p style={{ fontSize: 14, color: isDark ? '#91918c' : '#62625b', margin: 0 }}>
|
||||||
欢迎回来,这是您的系统概览
|
欢迎回来,这是您的系统概览
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -313,7 +313,7 @@ export default function Home() {
|
|||||||
<span style={{
|
<span style={{
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
}}>
|
}}>
|
||||||
{pendingTasks.length} 项待处理
|
{pendingTasks.length} 项待处理
|
||||||
</span>
|
</span>
|
||||||
@@ -340,7 +340,7 @@ export default function Home() {
|
|||||||
<span className={`erp-task-priority erp-task-priority-${task.priority}`}>
|
<span className={`erp-task-priority erp-task-priority-${task.priority}`}>
|
||||||
{priorityLabel[task.priority]}
|
{priorityLabel[task.priority]}
|
||||||
</span>
|
</span>
|
||||||
<RightOutlined style={{ color: isDark ? '#615d59' : '#CBD5E1', fontSize: 12 }} />
|
<RightOutlined style={{ color: isDark ? '#62625b' : '#CBD5E1', fontSize: 12 }} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -351,7 +351,7 @@ export default function Home() {
|
|||||||
<Col xs={24} lg={10}>
|
<Col xs={24} lg={10}>
|
||||||
<div className="erp-content-card erp-fade-in erp-fade-in-delay-3" style={{ height: '100%' }}>
|
<div className="erp-content-card erp-fade-in erp-fade-in-delay-3" style={{ height: '100%' }}>
|
||||||
<div className="erp-section-header">
|
<div className="erp-section-header">
|
||||||
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#62aef0' }} />
|
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#f05a5a' }} />
|
||||||
<span className="erp-section-title">最近动态</span>
|
<span className="erp-section-title">最近动态</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="erp-activity-list">
|
<div className="erp-activity-list">
|
||||||
@@ -400,7 +400,7 @@ export default function Home() {
|
|||||||
<Col xs={24} lg={8}>
|
<Col xs={24} lg={8}>
|
||||||
<div className="erp-content-card erp-fade-in erp-fade-in-delay-4" style={{ height: '100%' }}>
|
<div className="erp-content-card erp-fade-in erp-fade-in-delay-4" style={{ height: '100%' }}>
|
||||||
<div className="erp-section-header">
|
<div className="erp-section-header">
|
||||||
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#62aef0' }} />
|
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#f05a5a' }} />
|
||||||
<span className="erp-section-title">系统信息</span>
|
<span className="erp-section-title">系统信息</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="erp-system-info-list">
|
<div className="erp-system-info-list">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function Login() {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
background: 'linear-gradient(135deg, #312E81 0%, #0075de 50%, #62aef0 100%)',
|
background: 'linear-gradient(135deg, #312E81 0%, #e60023 50%, #f05a5a 100%)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -151,7 +151,7 @@ export default function Login() {
|
|||||||
<h2 style={{ marginBottom: 4, fontWeight: 700, fontSize: 24 }}>
|
<h2 style={{ marginBottom: 4, fontWeight: 700, fontSize: 24 }}>
|
||||||
欢迎回来
|
欢迎回来
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ fontSize: 14, color: '#615d59' }}>
|
<p style={{ fontSize: 14, color: '#62625b' }}>
|
||||||
请登录您的账户以继续
|
请登录您的账户以继续
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export default function Login() {
|
|||||||
rules={[{ required: true, message: '请输入用户名' }]}
|
rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<UserOutlined style={{ color: '#a39e98' }} />}
|
prefix={<UserOutlined style={{ color: '#91918c' }} />}
|
||||||
placeholder="用户名"
|
placeholder="用户名"
|
||||||
style={{ height: 44, borderRadius: 10 }}
|
style={{ height: 44, borderRadius: 10 }}
|
||||||
/>
|
/>
|
||||||
@@ -173,7 +173,7 @@ export default function Login() {
|
|||||||
rules={[{ required: true, message: '请输入密码' }]}
|
rules={[{ required: true, message: '请输入密码' }]}
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
prefix={<LockOutlined style={{ color: '#a39e98' }} />}
|
prefix={<LockOutlined style={{ color: '#91918c' }} />}
|
||||||
placeholder="密码"
|
placeholder="密码"
|
||||||
style={{ height: 44, borderRadius: 10 }}
|
style={{ height: 44, borderRadius: 10 }}
|
||||||
/>
|
/>
|
||||||
@@ -197,7 +197,7 @@ export default function Login() {
|
|||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<div style={{ marginTop: 32, textAlign: 'center' }}>
|
<div style={{ marginTop: 32, textAlign: 'center' }}>
|
||||||
<p style={{ fontSize: 12, color: '#615d59', margin: 0 }}>
|
<p style={{ fontSize: 12, color: '#62625b', margin: 0 }}>
|
||||||
ERP Platform v0.1.0 · Powered by Rust + React
|
ERP Platform v0.1.0 · Powered by Rust + React
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function Organizations() {
|
|||||||
const cardStyle = {
|
const cardStyle = {
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Org tree state ---
|
// --- Org tree state ---
|
||||||
@@ -264,9 +264,9 @@ export default function Organizations() {
|
|||||||
{item.name}{' '}
|
{item.name}{' '}
|
||||||
{item.code && <Tag style={{
|
{item.code && <Tag style={{
|
||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
background: isDark ? '#1e1e1d' : '#f2f9ff',
|
background: isDark ? '#211922' : '#fef0f0',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: '#0075de',
|
color: '#e60023',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
}}>{item.code}</Tag>}
|
}}>{item.code}</Tag>}
|
||||||
</span>
|
</span>
|
||||||
@@ -282,9 +282,9 @@ export default function Organizations() {
|
|||||||
{item.name}{' '}
|
{item.name}{' '}
|
||||||
{item.code && <Tag style={{
|
{item.code && <Tag style={{
|
||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
background: isDark ? '#1e1e1d' : '#ECFDF5',
|
background: isDark ? '#211922' : '#ECFDF5',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: '#1aae39',
|
color: '#103c25',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
}}>{item.code}</Tag>}
|
}}>{item.code}</Tag>}
|
||||||
</span>
|
</span>
|
||||||
@@ -343,7 +343,7 @@ export default function Organizations() {
|
|||||||
<div className="erp-page-header">
|
<div className="erp-page-header">
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
<ApartmentOutlined style={{ marginRight: 8, color: '#0075de' }} />
|
<ApartmentOutlined style={{ marginRight: 8, color: '#e60023' }} />
|
||||||
组织架构管理
|
组织架构管理
|
||||||
</h4>
|
</h4>
|
||||||
<div className="erp-page-subtitle">管理组织、部门和岗位的层级结构</div>
|
<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={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '14px 20px',
|
padding: '14px 20px',
|
||||||
borderBottom: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
borderBottom: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
@@ -418,7 +418,7 @@ export default function Organizations() {
|
|||||||
<div style={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
|
<div style={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '14px 20px',
|
padding: '14px 20px',
|
||||||
borderBottom: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
borderBottom: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
@@ -471,7 +471,7 @@ export default function Organizations() {
|
|||||||
<div style={{ flex: 1, ...cardStyle, overflow: 'hidden' }}>
|
<div style={{ flex: 1, ...cardStyle, overflow: 'hidden' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '14px 20px',
|
padding: '14px 20px',
|
||||||
borderBottom: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
borderBottom: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ import {
|
|||||||
import PluginSettingsForm from '../components/PluginSettingsForm';
|
import PluginSettingsForm from '../components/PluginSettingsForm';
|
||||||
|
|
||||||
const STATUS_CONFIG: Record<PluginStatus, { color: string; label: string }> = {
|
const STATUS_CONFIG: Record<PluginStatus, { color: string; label: string }> = {
|
||||||
uploaded: { color: '#615d59', label: '已上传' },
|
uploaded: { color: '#62625b', label: '已上传' },
|
||||||
installed: { color: '#2563EB', label: '已安装' },
|
installed: { color: '#2563EB', label: '已安装' },
|
||||||
enabled: { color: '#1aae39', label: '已启用' },
|
enabled: { color: '#103c25', label: '已启用' },
|
||||||
running: { color: '#1aae39', label: '运行中' },
|
running: { color: '#103c25', label: '运行中' },
|
||||||
disabled: { color: '#e5534b', label: '已禁用' },
|
disabled: { color: '#9e0a0a', label: '已禁用' },
|
||||||
uninstalled: { color: '#9333EA', label: '已卸载' },
|
uninstalled: { color: '#9333EA', label: '已卸载' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ export default function PluginAdmin() {
|
|||||||
key: 'status',
|
key: 'status',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (status: PluginStatus) => {
|
render: (status: PluginStatus) => {
|
||||||
const cfg = STATUS_CONFIG[status] || { color: '#615d59', label: status };
|
const cfg = STATUS_CONFIG[status] || { color: '#62625b', label: status };
|
||||||
return <Tag color={cfg.color}>{cfg.label}</Tag>;
|
return <Tag color={cfg.color}>{cfg.label}</Tag>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export function PluginDashboardPage() {
|
|||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: isDark ? '#f6f5f4' : 'rgba(0,0,0,0.95)',
|
color: isDark ? '#f6f6f3' : 'rgba(0,0,0,0.95)',
|
||||||
margin: '0 0 4px',
|
margin: '0 0 4px',
|
||||||
letterSpacing: '-0.5px',
|
letterSpacing: '-0.5px',
|
||||||
}}
|
}}
|
||||||
@@ -309,7 +309,7 @@ export function PluginDashboardPage() {
|
|||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -352,7 +352,7 @@ export function PluginDashboardPage() {
|
|||||||
<div className="erp-section-header">
|
<div className="erp-section-header">
|
||||||
<DashboardOutlined
|
<DashboardOutlined
|
||||||
className="erp-section-icon"
|
className="erp-section-icon"
|
||||||
style={{ color: '#0075de' }}
|
style={{ color: '#e60023' }}
|
||||||
/>
|
/>
|
||||||
<span className="erp-section-title">图表分析</span>
|
<span className="erp-section-title">图表分析</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -389,7 +389,7 @@ export function PluginDashboardPage() {
|
|||||||
<div className="erp-section-header">
|
<div className="erp-section-header">
|
||||||
<DashboardOutlined
|
<DashboardOutlined
|
||||||
className="erp-section-icon"
|
className="erp-section-icon"
|
||||||
style={{ color: currentPalette.tagColor === 'purple' ? '#0075de' : '#3B82F6' }}
|
style={{ color: currentPalette.tagColor === 'purple' ? '#e60023' : '#3B82F6' }}
|
||||||
/>
|
/>
|
||||||
<span className="erp-section-title">
|
<span className="erp-section-title">
|
||||||
{currentEntity?.display_name || selectedEntity} 数据分布
|
{currentEntity?.display_name || selectedEntity} 数据分布
|
||||||
|
|||||||
@@ -313,8 +313,8 @@ export function PluginGraphPage() {
|
|||||||
const r = degreeToRadius(degree, isCenter);
|
const r = degreeToRadius(degree, isCenter);
|
||||||
|
|
||||||
// Determine node color from its most common edge type, or default palette
|
// Determine node color from its most common edge type, or default palette
|
||||||
let nodeColorBase = '#0075de';
|
let nodeColorBase = '#e60023';
|
||||||
let nodeColorLight = '#62aef0';
|
let nodeColorLight = '#f05a5a';
|
||||||
let nodeColorGlow = 'rgba(79,70,229,0.3)';
|
let nodeColorGlow = 'rgba(79,70,229,0.3)';
|
||||||
|
|
||||||
if (isCenter) {
|
if (isCenter) {
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ import {
|
|||||||
const { Title, Text, Paragraph } = Typography;
|
const { Title, Text, Paragraph } = Typography;
|
||||||
|
|
||||||
const CATEGORY_COLORS: Record<string, string> = {
|
const CATEGORY_COLORS: Record<string, string> = {
|
||||||
'财务': '#1aae39',
|
'财务': '#103c25',
|
||||||
'CRM': '#2563EB',
|
'CRM': '#2563EB',
|
||||||
'进销存': '#9333EA',
|
'进销存': '#9333EA',
|
||||||
'生产': '#e5534b',
|
'生产': '#9e0a0a',
|
||||||
'人力资源': '#dd5b00',
|
'人力资源': '#b56e1a',
|
||||||
'基础': '#615d59',
|
'基础': '#62625b',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginMarket() {
|
export default function PluginMarket() {
|
||||||
@@ -190,7 +190,7 @@ export default function PluginMarket() {
|
|||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Text strong style={{ fontSize: 16 }}>{plugin.name}</Text>
|
<Text strong style={{ fontSize: 16 }}>{plugin.name}</Text>
|
||||||
<Tag
|
<Tag
|
||||||
color={CATEGORY_COLORS[plugin.category ?? ''] ?? '#615d59'}
|
color={CATEGORY_COLORS[plugin.category ?? ''] ?? '#62625b'}
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
>
|
>
|
||||||
{plugin.category}
|
{plugin.category}
|
||||||
@@ -244,7 +244,7 @@ export default function PluginMarket() {
|
|||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Tag color={CATEGORY_COLORS[selectedPlugin.category ?? ''] ?? '#615d59'}>
|
<Tag color={CATEGORY_COLORS[selectedPlugin.category ?? ''] ?? '#62625b'}>
|
||||||
{selectedPlugin.category}
|
{selectedPlugin.category}
|
||||||
</Tag>
|
</Tag>
|
||||||
<Text type="secondary">v{selectedPlugin.version}</Text>
|
<Text type="secondary">v{selectedPlugin.version}</Text>
|
||||||
|
|||||||
@@ -153,12 +153,12 @@ export default function Roles() {
|
|||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
background: record.is_system
|
background: record.is_system
|
||||||
? 'linear-gradient(135deg, #0075de, #62aef0)'
|
? 'linear-gradient(135deg, #e60023, #f05a5a)'
|
||||||
: isDark ? '#1e1e1d' : '#f6f5f4',
|
: isDark ? '#211922' : '#f6f6f3',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
color: record.is_system ? '#fff' : isDark ? '#a39e98' : '#615d59',
|
color: record.is_system ? '#fff' : isDark ? '#91918c' : '#62625b',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -174,9 +174,9 @@ export default function Roles() {
|
|||||||
key: 'code',
|
key: 'code',
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: isDark ? '#1e1e1d' : '#f6f5f4',
|
background: isDark ? '#211922' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
@@ -190,7 +190,7 @@ export default function Roles() {
|
|||||||
key: 'description',
|
key: 'description',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (v: string | undefined) => (
|
render: (v: string | undefined) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98' }}>{v || '-'}</span>
|
<span style={{ color: isDark ? '#62625b' : '#91918c' }}>{v || '-'}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,8 +201,8 @@ export default function Roles() {
|
|||||||
render: (v: boolean) => (
|
render: (v: boolean) => (
|
||||||
<Tag
|
<Tag
|
||||||
style={{
|
style={{
|
||||||
color: v ? '#0075de' : (isDark ? '#a39e98' : '#615d59'),
|
color: v ? '#e60023' : (isDark ? '#91918c' : '#62625b'),
|
||||||
background: v ? '#f2f9ff' : (isDark ? '#1e1e1d' : '#f6f5f4'),
|
background: v ? '#fef0f0' : (isDark ? '#211922' : '#f6f6f3'),
|
||||||
border: 'none',
|
border: 'none',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
}}
|
}}
|
||||||
@@ -222,7 +222,7 @@ export default function Roles() {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<SafetyCertificateOutlined />}
|
icon={<SafetyCertificateOutlined />}
|
||||||
onClick={() => openPermModal(record)}
|
onClick={() => openPermModal(record)}
|
||||||
style={{ color: '#0075de' }}
|
style={{ color: '#e60023' }}
|
||||||
>
|
>
|
||||||
权限
|
权限
|
||||||
</Button>
|
</Button>
|
||||||
@@ -233,7 +233,7 @@ export default function Roles() {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => openEditModal(record)}
|
onClick={() => openEditModal(record)}
|
||||||
style={{ color: isDark ? '#a39e98' : '#615d59' }}
|
style={{ color: isDark ? '#91918c' : '#62625b' }}
|
||||||
/>
|
/>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定删除此角色?"
|
title="确定删除此角色?"
|
||||||
@@ -279,7 +279,7 @@ export default function Roles() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
@@ -336,8 +336,8 @@ export default function Roles() {
|
|||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#E2E8F0'}`,
|
border: `1px solid ${isDark ? '#211922' : '#E2E8F0'}`,
|
||||||
background: isDark ? '#0B0F1A' : '#fafaf9',
|
background: isDark ? '#0B0F1A' : '#fafaf8',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ import { listRoles, type RoleInfo } from '../api/roles';
|
|||||||
import type { UserInfo } from '../api/auth';
|
import type { UserInfo } from '../api/auth';
|
||||||
|
|
||||||
const STATUS_COLOR_MAP: Record<string, string> = {
|
const STATUS_COLOR_MAP: Record<string, string> = {
|
||||||
active: '#1aae39',
|
active: '#103c25',
|
||||||
disabled: '#e5534b',
|
disabled: '#9e0a0a',
|
||||||
locked: '#dd5b00',
|
locked: '#b56e1a',
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_BG_MAP: Record<string, string> = {
|
const STATUS_BG_MAP: Record<string, string> = {
|
||||||
@@ -219,7 +219,7 @@ export default function Users() {
|
|||||||
width: 32,
|
width: 32,
|
||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
background: 'linear-gradient(135deg, #0075de, #62aef0)',
|
background: 'linear-gradient(135deg, #e60023, #f05a5a)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -233,7 +233,7 @@ export default function Users() {
|
|||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 500, fontSize: 14 }}>{v}</div>
|
<div style={{ fontWeight: 500, fontSize: 14 }}>{v}</div>
|
||||||
{record.display_name && (
|
{record.display_name && (
|
||||||
<div style={{ fontSize: 12, color: isDark ? '#615d59' : '#a39e98' }}>
|
<div style={{ fontSize: 12, color: isDark ? '#62625b' : '#91918c' }}>
|
||||||
{record.display_name}
|
{record.display_name}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -261,8 +261,8 @@ export default function Users() {
|
|||||||
render: (status: string) => (
|
render: (status: string) => (
|
||||||
<Tag
|
<Tag
|
||||||
style={{
|
style={{
|
||||||
color: STATUS_COLOR_MAP[status] || '#615d59',
|
color: STATUS_COLOR_MAP[status] || '#62625b',
|
||||||
background: STATUS_BG_MAP[status] || '#f6f5f4',
|
background: STATUS_BG_MAP[status] || '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
}}
|
}}
|
||||||
@@ -279,14 +279,14 @@ export default function Users() {
|
|||||||
roles.length > 0
|
roles.length > 0
|
||||||
? roles.map((r) => (
|
? roles.map((r) => (
|
||||||
<Tag key={r.id} style={{
|
<Tag key={r.id} style={{
|
||||||
background: isDark ? '#1e1e1d' : '#f6f5f4',
|
background: isDark ? '#211922' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#CBD5E1' : '#615d59',
|
color: isDark ? '#CBD5E1' : '#62625b',
|
||||||
}}>
|
}}>
|
||||||
{r.name}
|
{r.name}
|
||||||
</Tag>
|
</Tag>
|
||||||
))
|
))
|
||||||
: <span style={{ color: isDark ? '#615d59' : '#CBD5E1' }}>-</span>,
|
: <span style={{ color: isDark ? '#62625b' : '#CBD5E1' }}>-</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@@ -299,14 +299,14 @@ export default function Users() {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => openEditModal(record)}
|
onClick={() => openEditModal(record)}
|
||||||
style={{ color: isDark ? '#a39e98' : '#615d59' }}
|
style={{ color: isDark ? '#91918c' : '#62625b' }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
icon={<SafetyCertificateOutlined />}
|
icon={<SafetyCertificateOutlined />}
|
||||||
onClick={() => openRoleModal(record)}
|
onClick={() => openRoleModal(record)}
|
||||||
style={{ color: isDark ? '#a39e98' : '#615d59' }}
|
style={{ color: isDark ? '#91918c' : '#62625b' }}
|
||||||
/>
|
/>
|
||||||
{record.status === 'active' ? (
|
{record.status === 'active' ? (
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@@ -326,7 +326,7 @@ export default function Users() {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<CheckCircleOutlined />}
|
icon={<CheckCircleOutlined />}
|
||||||
onClick={() => handleToggleStatus(record.id, 'active')}
|
onClick={() => handleToggleStatus(record.id, 'active')}
|
||||||
style={{ color: '#1aae39' }}
|
style={{ color: '#103c25' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@@ -356,7 +356,7 @@ export default function Users() {
|
|||||||
<Space size={8}>
|
<Space size={8}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索用户名..."
|
placeholder="搜索用户名..."
|
||||||
prefix={<SearchOutlined style={{ color: '#a39e98' }} />}
|
prefix={<SearchOutlined style={{ color: '#91918c' }} />}
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSearchText(e.target.value);
|
setSearchText(e.target.value);
|
||||||
@@ -379,7 +379,7 @@ export default function Users() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
@@ -415,7 +415,7 @@ export default function Users() {
|
|||||||
label="用户名"
|
label="用户名"
|
||||||
rules={[{ required: true, message: '请输入用户名' }]}
|
rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
>
|
>
|
||||||
<Input prefix={<UserOutlined style={{ color: '#a39e98' }} />} disabled={!!editUser} />
|
<Input prefix={<UserOutlined style={{ color: '#91918c' }} />} disabled={!!editUser} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{!editUser && (
|
{!editUser && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -465,13 +465,13 @@ export default function Users() {
|
|||||||
style={{
|
style={{
|
||||||
padding: '10px 14px',
|
padding: '10px 14px',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#E2E8F0'}`,
|
border: `1px solid ${isDark ? '#211922' : '#E2E8F0'}`,
|
||||||
background: isDark ? '#0B0F1A' : '#fafaf9',
|
background: isDark ? '#0B0F1A' : '#fafaf8',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Checkbox value={r.id}>
|
<Checkbox value={r.id}>
|
||||||
<span style={{ fontWeight: 500 }}>{r.name}</span>
|
<span style={{ fontWeight: 500 }}>{r.name}</span>
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', marginLeft: 8, fontSize: 12 }}>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', marginLeft: 8, fontSize: 12 }}>
|
||||||
{r.code}
|
{r.code}
|
||||||
</span>
|
</span>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ function prepareChartData(data: WidgetData['data'], dimensionOrder?: string[]) {
|
|||||||
const TAG_COLOR_MAP: Record<string, string> = {
|
const TAG_COLOR_MAP: Record<string, string> = {
|
||||||
blue: '#3B82F6', green: '#10B981', orange: '#F59E0B', red: '#EF4444',
|
blue: '#3B82F6', green: '#10B981', orange: '#F59E0B', red: '#EF4444',
|
||||||
purple: '#8B5CF6', cyan: '#06B6D4', magenta: '#EC4899', gold: '#EAB308',
|
purple: '#8B5CF6', cyan: '#06B6D4', magenta: '#EC4899', gold: '#EAB308',
|
||||||
lime: '#84CC16', geekblue: '#62aef0', volcano: '#F97316',
|
lime: '#84CC16', geekblue: '#f05a5a', volcano: '#F97316',
|
||||||
};
|
};
|
||||||
|
|
||||||
function tagStrokeColor(color: string): string {
|
function tagStrokeColor(color: string): string {
|
||||||
@@ -204,7 +204,7 @@ export function SkeletonBreakdownCard({ index }: { index: number }) {
|
|||||||
function StatWidgetCard({ widgetData }: { widgetData: WidgetData }) {
|
function StatWidgetCard({ widgetData }: { widgetData: WidgetData }) {
|
||||||
const { widget, count } = widgetData;
|
const { widget, count } = widgetData;
|
||||||
const animatedValue = useCountUp(count ?? 0);
|
const animatedValue = useCountUp(count ?? 0);
|
||||||
const color = widget.color || '#0075de';
|
const color = widget.color || '#e60023';
|
||||||
return (
|
return (
|
||||||
<Card size="small" className="erp-fade-in" style={{ height: '100%' }}>
|
<Card size="small" className="erp-fade-in" style={{ height: '100%' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<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 }) {
|
function BarWidgetCard({ widgetData, isDark }: { widgetData: WidgetData; isDark: boolean }) {
|
||||||
const { widget, data } = widgetData;
|
const { widget, data } = widgetData;
|
||||||
const chartData = prepareChartData(data, widget.dimension_order);
|
const chartData = prepareChartData(data, widget.dimension_order);
|
||||||
const axisLabelStyle = { fill: isDark ? '#a39e98' : '#615d59' };
|
const axisLabelStyle = { fill: isDark ? '#91918c' : '#62625b' };
|
||||||
return (
|
return (
|
||||||
<WidgetCardShell title={widget.title} widgetType={widget.type}>
|
<WidgetCardShell title={widget.title} widgetType={widget.type}>
|
||||||
{chartData.length > 0 ? (
|
{chartData.length > 0 ? (
|
||||||
@@ -275,7 +275,7 @@ function FunnelWidgetCard({ widgetData }: { widgetData: WidgetData }) {
|
|||||||
function LineWidgetCard({ widgetData, isDark }: { widgetData: WidgetData; isDark: boolean }) {
|
function LineWidgetCard({ widgetData, isDark }: { widgetData: WidgetData; isDark: boolean }) {
|
||||||
const { widget, data } = widgetData;
|
const { widget, data } = widgetData;
|
||||||
const chartData = prepareChartData(data, widget.dimension_order);
|
const chartData = prepareChartData(data, widget.dimension_order);
|
||||||
const axisLabelStyle = { fill: isDark ? '#a39e98' : '#615d59' };
|
const axisLabelStyle = { fill: isDark ? '#91918c' : '#62625b' };
|
||||||
return (
|
return (
|
||||||
<WidgetCardShell title={widget.title} widgetType={widget.type}>
|
<WidgetCardShell title={widget.title} widgetType={widget.type}>
|
||||||
{chartData.length > 0 ? (
|
{chartData.length > 0 ? (
|
||||||
@@ -315,7 +315,7 @@ function StatCardsWidget({ widgetData }: { widgetData: WidgetData }) {
|
|||||||
{statCards.map((sc, i) => (
|
{statCards.map((sc, i) => (
|
||||||
<Col xs={12} sm={6} key={`${sc.card.entity}-${sc.card.label}-${i}`}>
|
<Col xs={12} sm={6} key={`${sc.card.entity}-${sc.card.label}-${i}`}>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: `${sc.card.color || '#0075de'}10`,
|
background: `${sc.card.color || '#e60023'}10`,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -324,9 +324,9 @@ function StatCardsWidget({ widgetData }: { widgetData: WidgetData }) {
|
|||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 36, height: 36, borderRadius: 8,
|
width: 36, height: 36, borderRadius: 8,
|
||||||
background: `${sc.card.color || '#0075de'}20`,
|
background: `${sc.card.color || '#e60023'}20`,
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
color: sc.card.color || '#0075de', fontSize: 18,
|
color: sc.card.color || '#e60023', fontSize: 18,
|
||||||
}}>
|
}}>
|
||||||
<DashboardOutlined />
|
<DashboardOutlined />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import {
|
|||||||
// ── 通用调色板 ──
|
// ── 通用调色板 ──
|
||||||
|
|
||||||
const UNIVERSAL_COLORS = [
|
const UNIVERSAL_COLORS = [
|
||||||
{ gradient: 'linear-gradient(135deg, #0075de, #62aef0)', iconBg: 'rgba(79, 70, 229, 0.12)', tagColor: 'purple' },
|
{ gradient: 'linear-gradient(135deg, #e60023, #f05a5a)', 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, #103c25, #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, #b56e1a, #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, #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, #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' },
|
{ gradient: 'linear-gradient(135deg, #0891B2, #06B6D4)', iconBg: 'rgba(8, 145, 178, 0.12)', tagColor: 'cyan' },
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import type { GraphEdge } from './graphTypes';
|
|||||||
|
|
||||||
/** 关系类型对应的色板 (base / light / glow) — 通用调色板自动分配 */
|
/** 关系类型对应的色板 (base / light / glow) — 通用调色板自动分配 */
|
||||||
const EDGE_PALETTE: Array<{ base: string; light: string; glow: string }> = [
|
const EDGE_PALETTE: Array<{ base: string; light: string; glow: string }> = [
|
||||||
{ base: '#0075de', light: '#62aef0', glow: 'rgba(79,70,229,0.3)' },
|
{ base: '#e60023', light: '#f05a5a', glow: 'rgba(79,70,229,0.3)' },
|
||||||
{ base: '#1aae39', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
{ base: '#103c25', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
||||||
{ base: '#dd5b00', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
|
{ base: '#b56e1a', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
|
||||||
{ base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
|
{ base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
|
||||||
{ base: '#e5534b', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
|
{ base: '#9e0a0a', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
|
||||||
{ base: '#7C3AED', light: '#A78BFA', glow: 'rgba(124,58,237,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: '#EA580C', light: '#FB923C', glow: 'rgba(234,88,12,0.3)' },
|
||||||
{ base: '#DB2777', light: '#F472B6', glow: 'rgba(219,39,119,0.3)' },
|
{ base: '#DB2777', light: '#F472B6', glow: 'rgba(219,39,119,0.3)' },
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import type { ColumnsType } from 'antd/es/table';
|
|||||||
import { listTemplates, createTemplate, type MessageTemplateInfo } from '../../api/messageTemplates';
|
import { listTemplates, createTemplate, type MessageTemplateInfo } from '../../api/messageTemplates';
|
||||||
|
|
||||||
const channelMap: Record<string, { label: string; color: string }> = {
|
const channelMap: Record<string, { label: string; color: string }> = {
|
||||||
in_app: { label: '站内', color: '#0075de' },
|
in_app: { label: '站内', color: '#e60023' },
|
||||||
email: { label: '邮件', color: '#1aae39' },
|
email: { label: '邮件', color: '#103c25' },
|
||||||
sms: { label: '短信', color: '#dd5b00' },
|
sms: { label: '短信', color: '#b56e1a' },
|
||||||
wechat: { label: '微信', color: '#7C3AED' },
|
wechat: { label: '微信', color: '#7C3AED' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,9 +64,9 @@ export default function MessageTemplates() {
|
|||||||
key: 'code',
|
key: 'code',
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: isDark ? '#1e1e1d' : '#f6f5f4',
|
background: isDark ? '#211922' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
@@ -80,7 +80,7 @@ export default function MessageTemplates() {
|
|||||||
key: 'channel',
|
key: 'channel',
|
||||||
width: 90,
|
width: 90,
|
||||||
render: (c: string) => {
|
render: (c: string) => {
|
||||||
const info = channelMap[c] || { label: c, color: '#615d59' };
|
const info = channelMap[c] || { label: c, color: '#62625b' };
|
||||||
return (
|
return (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: info.color + '15',
|
background: info.color + '15',
|
||||||
@@ -111,7 +111,7 @@ export default function MessageTemplates() {
|
|||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>{v}</span>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', fontSize: 13 }}>{v}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -124,7 +124,7 @@ export default function MessageTemplates() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#62625b' : '#91918c' }}>
|
||||||
共 {total} 个模板
|
共 {total} 个模板
|
||||||
</span>
|
</span>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setModalOpen(true)}>
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => setModalOpen(true)}>
|
||||||
@@ -135,7 +135,7 @@ export default function MessageTemplates() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const priorityStyles: Record<string, { bg: string; color: string; text: string }> = {
|
const priorityStyles: Record<string, { bg: string; color: string; text: string }> = {
|
||||||
urgent: { bg: '#FEF2F2', color: '#e5534b', text: '紧急' },
|
urgent: { bg: '#FEF2F2', color: '#9e0a0a', text: '紧急' },
|
||||||
important: { bg: '#FFFBEB', color: '#dd5b00', text: '重要' },
|
important: { bg: '#FFFBEB', color: '#b56e1a', text: '重要' },
|
||||||
normal: { bg: '#f2f9ff', color: '#0075de', text: '普通' },
|
normal: { bg: '#fef0f0', color: '#e60023', text: '普通' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NotificationList({ queryFilter }: Props) {
|
export default function NotificationList({ queryFilter }: Props) {
|
||||||
@@ -83,7 +83,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<Paragraph>{record.body}</Paragraph>
|
<Paragraph>{record.body}</Paragraph>
|
||||||
<div style={{ marginTop: 8, color: isDark ? '#615d59' : '#a39e98', fontSize: 12 }}>
|
<div style={{ marginTop: 8, color: isDark ? '#62625b' : '#91918c', fontSize: 12 }}>
|
||||||
{record.created_at}
|
{record.created_at}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +104,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
style={{
|
style={{
|
||||||
fontWeight: record.is_read ? 400 : 600,
|
fontWeight: record.is_read ? 400 : 600,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
color: record.is_read ? (isDark ? '#a39e98' : '#615d59') : 'inherit',
|
color: record.is_read ? (isDark ? '#91918c' : '#62625b') : 'inherit',
|
||||||
}}
|
}}
|
||||||
onClick={() => showDetail(record)}
|
onClick={() => showDetail(record)}
|
||||||
>
|
>
|
||||||
@@ -114,7 +114,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: '#0075de',
|
background: '#e60023',
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
}} />
|
}} />
|
||||||
)}
|
)}
|
||||||
@@ -128,7 +128,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
key: 'priority',
|
key: 'priority',
|
||||||
width: 90,
|
width: 90,
|
||||||
render: (p: string) => {
|
render: (p: string) => {
|
||||||
const info = priorityStyles[p] || { bg: '#f6f5f4', color: '#615d59', text: p };
|
const info = priorityStyles[p] || { bg: '#f6f6f3', color: '#62625b', text: p };
|
||||||
return (
|
return (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: info.bg,
|
background: info.bg,
|
||||||
@@ -146,7 +146,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
dataIndex: 'sender_type',
|
dataIndex: 'sender_type',
|
||||||
key: 'sender_type',
|
key: 'sender_type',
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (s: string) => <span style={{ color: isDark ? '#615d59' : '#a39e98' }}>{s === 'system' ? '系统' : '用户'}</span>,
|
render: (s: string) => <span style={{ color: isDark ? '#62625b' : '#91918c' }}>{s === 'system' ? '系统' : '用户'}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
@@ -155,9 +155,9 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
width: 80,
|
width: 80,
|
||||||
render: (r: boolean) => (
|
render: (r: boolean) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: r ? (isDark ? '#1e1e1d' : '#f6f5f4') : '#f2f9ff',
|
background: r ? (isDark ? '#211922' : '#f6f6f3') : '#fef0f0',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: r ? (isDark ? '#615d59' : '#a39e98') : '#0075de',
|
color: r ? (isDark ? '#62625b' : '#91918c') : '#e60023',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
}}>
|
}}>
|
||||||
{r ? '已读' : '未读'}
|
{r ? '已读' : '未读'}
|
||||||
@@ -170,7 +170,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>{v}</span>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', fontSize: 13 }}>{v}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -185,7 +185,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
size="small"
|
size="small"
|
||||||
icon={<CheckOutlined />}
|
icon={<CheckOutlined />}
|
||||||
onClick={() => handleMarkRead(record.id)}
|
onClick={() => handleMarkRead(record.id)}
|
||||||
style={{ color: '#0075de' }}
|
style={{ color: '#e60023' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -193,7 +193,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
size="small"
|
size="small"
|
||||||
icon={<EyeOutlined />}
|
icon={<EyeOutlined />}
|
||||||
onClick={() => showDetail(record)}
|
onClick={() => showDetail(record)}
|
||||||
style={{ color: isDark ? '#615d59' : '#a39e98' }}
|
style={{ color: isDark ? '#62625b' : '#91918c' }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
@@ -215,7 +215,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#62625b' : '#91918c' }}>
|
||||||
共 {total} 条消息
|
共 {total} 条消息
|
||||||
</span>
|
</span>
|
||||||
<Button icon={<CheckOutlined />} onClick={handleMarkAllRead}>
|
<Button icon={<CheckOutlined />} onClick={handleMarkAllRead}>
|
||||||
@@ -226,7 +226,7 @@ export default function NotificationList({ queryFilter }: Props) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ export default function NotificationPreferences() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
padding: 24,
|
padding: 24,
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20 }}>
|
||||||
<BellOutlined style={{ fontSize: 16, color: '#0075de' }} />
|
<BellOutlined style={{ fontSize: 16, color: '#e60023' }} />
|
||||||
<span style={{ fontSize: 15, fontWeight: 600 }}>通知偏好设置</span>
|
<span style={{ fontSize: 15, fontWeight: 600 }}>通知偏好设置</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
// 通用边调色板
|
// 通用边调色板
|
||||||
const EDGE_PALETTE: Array<{ base: string; light: string; glow: string }> = [
|
const EDGE_PALETTE: Array<{ base: string; light: string; glow: string }> = [
|
||||||
{ base: '#0075de', light: '#62aef0', glow: 'rgba(79,70,229,0.3)' },
|
{ base: '#e60023', light: '#f05a5a', glow: 'rgba(79,70,229,0.3)' },
|
||||||
{ base: '#1aae39', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
{ base: '#103c25', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
||||||
{ base: '#dd5b00', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
|
{ base: '#b56e1a', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
|
||||||
{ base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
|
{ base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
|
||||||
{ base: '#e5534b', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
|
{ base: '#9e0a0a', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
|
||||||
{ base: '#7C3AED', light: '#A78BFA', glow: 'rgba(124,58,237,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: '#EA580C', light: '#FB923C', glow: 'rgba(234,88,12,0.3)' },
|
||||||
{ base: '#DB2777', light: '#F472B6', glow: 'rgba(219,39,119,0.3)' },
|
{ base: '#DB2777', light: '#F472B6', glow: 'rgba(219,39,119,0.3)' },
|
||||||
|
|||||||
@@ -295,8 +295,8 @@ export function drawFullGraph(
|
|||||||
const degree = degreeMap.get(node.id) || 0;
|
const degree = degreeMap.get(node.id) || 0;
|
||||||
const r = degreeToRadius(degree, isCenter);
|
const r = degreeToRadius(degree, isCenter);
|
||||||
|
|
||||||
let nodeColorBase = '#0075de';
|
let nodeColorBase = '#e60023';
|
||||||
let nodeColorLight = '#62aef0';
|
let nodeColorLight = '#f05a5a';
|
||||||
let nodeColorGlow = 'rgba(79,70,229,0.3)';
|
let nodeColorGlow = 'rgba(79,70,229,0.3)';
|
||||||
|
|
||||||
if (isCenter) {
|
if (isCenter) {
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ const RESOURCE_TYPE_OPTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const ACTION_STYLES: Record<string, { bg: string; color: string; text: string }> = {
|
const ACTION_STYLES: Record<string, { bg: string; color: string; text: string }> = {
|
||||||
create: { bg: '#ECFDF5', color: '#1aae39', text: '创建' },
|
create: { bg: '#ECFDF5', color: '#103c25', text: '创建' },
|
||||||
update: { bg: '#f2f9ff', color: '#0075de', text: '更新' },
|
update: { bg: '#fef0f0', color: '#e60023', text: '更新' },
|
||||||
delete: { bg: '#FEF2F2', color: '#e5534b', text: '删除' },
|
delete: { bg: '#FEF2F2', color: '#9e0a0a', text: '删除' },
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDateTime(value: string): string {
|
function formatDateTime(value: string): string {
|
||||||
@@ -80,7 +80,7 @@ export default function AuditLogViewer() {
|
|||||||
key: 'action',
|
key: 'action',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (action: string) => {
|
render: (action: string) => {
|
||||||
const info = ACTION_STYLES[action] || { bg: '#f6f5f4', color: '#615d59', text: action };
|
const info = ACTION_STYLES[action] || { bg: '#f6f6f3', color: '#62625b', text: action };
|
||||||
return (
|
return (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: info.bg,
|
background: info.bg,
|
||||||
@@ -100,9 +100,9 @@ export default function AuditLogViewer() {
|
|||||||
width: 120,
|
width: 120,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: isDark ? '#1e1e1d' : '#f6f5f4',
|
background: isDark ? '#211922' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#CBD5E1' : '#615d59',
|
color: isDark ? '#CBD5E1' : '#62625b',
|
||||||
}}>
|
}}>
|
||||||
{v}
|
{v}
|
||||||
</Tag>
|
</Tag>
|
||||||
@@ -115,7 +115,7 @@ export default function AuditLogViewer() {
|
|||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#a39e98' : '#615d59' }}>
|
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#91918c' : '#62625b' }}>
|
||||||
{v}
|
{v}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -127,7 +127,7 @@ export default function AuditLogViewer() {
|
|||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#a39e98' : '#615d59' }}>
|
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#91918c' : '#62625b' }}>
|
||||||
{v}
|
{v}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -138,7 +138,7 @@ export default function AuditLogViewer() {
|
|||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (value: string) => (
|
render: (value: string) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', fontSize: 13 }}>
|
||||||
{formatDateTime(value)}
|
{formatDateTime(value)}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -156,7 +156,7 @@ export default function AuditLogViewer() {
|
|||||||
padding: 12,
|
padding: 12,
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
}}>
|
}}>
|
||||||
<Select
|
<Select
|
||||||
allowClear
|
allowClear
|
||||||
@@ -173,7 +173,7 @@ export default function AuditLogViewer() {
|
|||||||
value={query.user_id ?? ''}
|
value={query.user_id ?? ''}
|
||||||
onChange={(e) => handleFilterChange('user_id', e.target.value)}
|
onChange={(e) => handleFilterChange('user_id', e.target.value)}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98', marginLeft: 'auto' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#62625b' : '#91918c', marginLeft: 'auto' }}>
|
||||||
共 {total} 条日志
|
共 {total} 条日志
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,7 +182,7 @@ export default function AuditLogViewer() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -132,9 +132,9 @@ export default function SystemSettings() {
|
|||||||
width: 250,
|
width: 250,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: isDark ? '#1e1e1d' : '#f6f5f4',
|
background: isDark ? '#211922' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#CBD5E1' : '#615d59',
|
color: isDark ? '#CBD5E1' : '#62625b',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
@@ -162,7 +162,7 @@ export default function SystemSettings() {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => openEdit(record)}
|
onClick={() => openEdit(record)}
|
||||||
style={{ color: isDark ? '#a39e98' : '#615d59' }}
|
style={{ color: isDark ? '#91918c' : '#62625b' }}
|
||||||
/>
|
/>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定删除此设置?"
|
title="确定删除此设置?"
|
||||||
@@ -191,7 +191,7 @@ export default function SystemSettings() {
|
|||||||
<Space>
|
<Space>
|
||||||
<Input
|
<Input
|
||||||
placeholder="输入设置键名查询"
|
placeholder="输入设置键名查询"
|
||||||
prefix={<SearchOutlined style={{ color: '#a39e98' }} />}
|
prefix={<SearchOutlined style={{ color: '#91918c' }} />}
|
||||||
value={searchKey}
|
value={searchKey}
|
||||||
onChange={(e) => setSearchKey(e.target.value)}
|
onChange={(e) => setSearchKey(e.target.value)}
|
||||||
onPressEnter={handleSearch}
|
onPressEnter={handleSearch}
|
||||||
@@ -207,7 +207,7 @@ export default function SystemSettings() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import type { ColumnsType } from 'antd/es/table';
|
|||||||
import { listCompletedTasks, type TaskInfo } from '../../api/workflowTasks';
|
import { listCompletedTasks, type TaskInfo } from '../../api/workflowTasks';
|
||||||
|
|
||||||
const outcomeStyles: Record<string, { bg: string; color: string; text: string }> = {
|
const outcomeStyles: Record<string, { bg: string; color: string; text: string }> = {
|
||||||
approved: { bg: '#ECFDF5', color: '#1aae39', text: '同意' },
|
approved: { bg: '#ECFDF5', color: '#103c25', text: '同意' },
|
||||||
rejected: { bg: '#FEF2F2', color: '#e5534b', text: '拒绝' },
|
rejected: { bg: '#FEF2F2', color: '#9e0a0a', text: '拒绝' },
|
||||||
delegated: { bg: '#f2f9ff', color: '#0075de', text: '已委派' },
|
delegated: { bg: '#fef0f0', color: '#e60023', text: '已委派' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CompletedTasks() {
|
export default function CompletedTasks() {
|
||||||
@@ -50,7 +50,7 @@ export default function CompletedTasks() {
|
|||||||
key: 'outcome',
|
key: 'outcome',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (o: string) => {
|
render: (o: string) => {
|
||||||
const info = outcomeStyles[o] || { bg: '#f6f5f4', color: '#615d59', text: o };
|
const info = outcomeStyles[o] || { bg: '#f6f6f3', color: '#62625b', text: o };
|
||||||
return (
|
return (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: info.bg,
|
background: info.bg,
|
||||||
@@ -69,7 +69,7 @@ export default function CompletedTasks() {
|
|||||||
key: 'completed_at',
|
key: 'completed_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', fontSize: 13 }}>
|
||||||
{v ? new Date(v).toLocaleString() : '-'}
|
{v ? new Date(v).toLocaleString() : '-'}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -80,7 +80,7 @@ export default function CompletedTasks() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import { getProcessDefinition, type NodeDef, type EdgeDef } from '../../api/work
|
|||||||
import ProcessViewer from './ProcessViewer';
|
import ProcessViewer from './ProcessViewer';
|
||||||
|
|
||||||
const statusStyles: Record<string, { bg: string; color: string; text: string }> = {
|
const statusStyles: Record<string, { bg: string; color: string; text: string }> = {
|
||||||
running: { bg: '#f2f9ff', color: '#0075de', text: '运行中' },
|
running: { bg: '#fef0f0', color: '#e60023', text: '运行中' },
|
||||||
suspended: { bg: '#FFFBEB', color: '#dd5b00', text: '已挂起' },
|
suspended: { bg: '#FFFBEB', color: '#b56e1a', text: '已挂起' },
|
||||||
completed: { bg: '#ECFDF5', color: '#1aae39', text: '已完成' },
|
completed: { bg: '#ECFDF5', color: '#103c25', text: '已完成' },
|
||||||
terminated: { bg: '#FEF2F2', color: '#e5534b', text: '已终止' },
|
terminated: { bg: '#FEF2F2', color: '#9e0a0a', text: '已终止' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InstanceMonitor() {
|
export default function InstanceMonitor() {
|
||||||
@@ -129,7 +129,7 @@ export default function InstanceMonitor() {
|
|||||||
key: 'status',
|
key: 'status',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (s: string) => {
|
render: (s: string) => {
|
||||||
const info = statusStyles[s] || { bg: '#f6f5f4', color: '#615d59', text: s };
|
const info = statusStyles[s] || { bg: '#f6f6f3', color: '#62625b', text: s };
|
||||||
return (
|
return (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: info.bg,
|
background: info.bg,
|
||||||
@@ -154,7 +154,7 @@ export default function InstanceMonitor() {
|
|||||||
key: 'started_at',
|
key: 'started_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', fontSize: 13 }}>
|
||||||
{new Date(v).toLocaleString()}
|
{new Date(v).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -214,7 +214,7 @@ export default function InstanceMonitor() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ export default function PendingTasks() {
|
|||||||
key: 'business_key',
|
key: 'business_key',
|
||||||
render: (v: string | undefined) => v ? (
|
render: (v: string | undefined) => v ? (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: isDark ? '#1e1e1d' : '#f6f5f4',
|
background: isDark ? '#211922' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
@@ -93,9 +93,9 @@ export default function PendingTasks() {
|
|||||||
width: 100,
|
width: 100,
|
||||||
render: (s: string) => (
|
render: (s: string) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: '#f2f9ff',
|
background: '#fef0f0',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: '#0075de',
|
color: '#e60023',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
}}>
|
}}>
|
||||||
{s}
|
{s}
|
||||||
@@ -108,7 +108,7 @@ export default function PendingTasks() {
|
|||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ color: isDark ? '#615d59' : '#a39e98', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#62625b' : '#91918c', fontSize: 13 }}>
|
||||||
{new Date(v).toLocaleString()}
|
{new Date(v).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -145,7 +145,7 @@ export default function PendingTasks() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1e1e1d' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#211922' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
import ProcessDesigner from './ProcessDesigner';
|
import ProcessDesigner from './ProcessDesigner';
|
||||||
|
|
||||||
const statusColors: Record<string, { bg: string; color: string; text: string }> = {
|
const statusColors: Record<string, { bg: string; color: string; text: string }> = {
|
||||||
draft: { bg: '#f6f5f4', color: '#615d59', text: '草稿' },
|
draft: { bg: '#f6f6f3', color: '#62625b', text: '草稿' },
|
||||||
published: { bg: '#ecfdf5', color: '#1aae39', text: '已发布' },
|
published: { bg: '#ecfdf5', color: '#103c25', text: '已发布' },
|
||||||
deprecated: { bg: '#fef2f2', color: '#e5534b', text: '已弃用' },
|
deprecated: { bg: '#fef2f2', color: '#9e0a0a', text: '已弃用' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProcessDefinitions() {
|
export default function ProcessDefinitions() {
|
||||||
@@ -92,9 +92,9 @@ export default function ProcessDefinitions() {
|
|||||||
key: 'key',
|
key: 'key',
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: isDark ? '#1E293B' : '#f6f5f4',
|
background: isDark ? '#1E293B' : '#f6f6f3',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#a39e98' : '#615d59',
|
color: isDark ? '#91918c' : '#62625b',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
@@ -110,7 +110,7 @@ export default function ProcessDefinitions() {
|
|||||||
key: 'status',
|
key: 'status',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (s: string) => {
|
render: (s: string) => {
|
||||||
const info = statusColors[s] || { bg: '#f6f5f4', color: '#615d59', text: s };
|
const info = statusColors[s] || { bg: '#f6f6f3', color: '#62625b', text: s };
|
||||||
return (
|
return (
|
||||||
<Tag style={{
|
<Tag style={{
|
||||||
background: info.bg,
|
background: info.bg,
|
||||||
@@ -152,7 +152,7 @@ export default function ProcessDefinitions() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 13, color: isDark ? '#615d59' : '#a39e98' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#62625b' : '#91918c' }}>
|
||||||
共 {total} 个流程定义
|
共 {total} 个流程定义
|
||||||
</span>
|
</span>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
||||||
@@ -163,7 +163,7 @@ export default function ProcessDefinitions() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
background: isDark ? '#111827' : '#FFFFFF',
|
background: isDark ? '#111827' : '#FFFFFF',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
border: `1px solid ${isDark ? '#1E293B' : '#f6f5f4'}`,
|
border: `1px solid ${isDark ? '#1E293B' : '#f6f6f3'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
Reference in New Issue
Block a user