feat(web): 采用 UI UX Pro Max Soft UI Evolution 设计系统
从 Pinterest 风格切换到 Soft UI Evolution 设计系统,使用 UI UX Pro Max 推理引擎生成适合跨行业 ERP 业务用户的专业设计方案。 设计变更: - 主色从 Pinterest Red (#e60023) 切换到 Trust Blue (#2563EB) - 字体从系统默认切换到 Noto Sans SC(中文优先) - 圆角从 16-20px 调整到 10-12px(专业但不夸张) - 中性色从暖橄榄调切换到 Slate 石板蓝调 - 成功色 #103c25 → #059669,警告色 #b56e1a → #d97706 - 暗色模式从暖黑 (#1a1a18) 切换到深海军蓝 (#0f172a) 涉及文件:DESIGN.md + index.css + App.tsx + 24 个组件文件
This commit is contained in:
396
DESIGN.md
396
DESIGN.md
@@ -1,263 +1,273 @@
|
|||||||
# Design System Inspired by Pinterest
|
# Design System — Enterprise ERP Platform
|
||||||
|
|
||||||
|
> Generated by UI UX Pro Max | Style: Soft UI Evolution | Target: Cross-industry business users
|
||||||
|
|
||||||
## 1. Visual Theme & Atmosphere
|
## 1. Visual Theme & Atmosphere
|
||||||
|
|
||||||
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.
|
A warm, professional, and approachable design that feels trustworthy for business users across all industries — manufacturing, retail, finance, services, and more. The design uses a clean white canvas with soft blue as the primary brand color, conveying reliability and clarity without feeling cold or corporate.
|
||||||
|
|
||||||
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.
|
The Soft UI Evolution style provides subtle depth through improved shadows — softer than flat design but clearer than neumorphism. This creates a sense of modern polish that invites interaction while maintaining excellent readability and accessibility (WCAG AA+).
|
||||||
|
|
||||||
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:**
|
||||||
- Warm white canvas with olive/sand-toned neutrals — cozy, not clinical
|
- Clean white canvas with soft blue accent — trustworthy, professional, warm
|
||||||
- Pinterest Red (`#e60023`) as singular bold accent — never subtle, always confident
|
- Subtle depth through soft shadows — modern but not flat, not skeuomorphic
|
||||||
- Pin Sans custom font with global fallback stack (including CJK)
|
- Moderate border-radius (10-12px) — friendly but not playful
|
||||||
- Three-tier token architecture: `--comp-*` / `--sema-*` / `--base-*`
|
- Chinese-first typography with Noto Sans SC — readable at all sizes
|
||||||
- Warm secondary surfaces: sand gray (`#e5e5e0`), warm badge (`hsla(60,20%,98%,.5)`)
|
- Two-tier token system: CSS Variables (`--erp-*`) + Ant Design ConfigProvider
|
||||||
- Generous border-radius: 16px standard, up to 40px for large containers
|
- Dual theme support: light (default) + dark mode
|
||||||
- Photography-first content — pins/images are the primary visual element
|
|
||||||
- Dark near-purple text (`#211922`) — warm, with a hint of plum
|
|
||||||
|
|
||||||
## 2. Color Palette & Roles
|
## 2. Color Palette & Roles
|
||||||
|
|
||||||
### Primary Brand
|
### Primary Brand
|
||||||
- **Pinterest Red** (`#e60023`): Primary CTA, brand accent — bold, confident red
|
- **Primary Blue** (`#2563EB`): Primary CTA, active states, links, brand accent
|
||||||
- **Green 700** (`#103c25`): `--base-color-green-700`, success/nature accent
|
- **Primary Hover** (`#1D4ED8`): Pressed/active primary state
|
||||||
- **Green 700 Hover** (`#0b2819`): `--base-color-hover-green-700`, pressed green
|
- **Primary Light** (`#EFF6FF`): Primary background tint, subtle highlights
|
||||||
|
- **Primary Subtle** (`#DBEAFE`): Light blue backgrounds for badges, chips
|
||||||
|
|
||||||
|
### Semantic Colors
|
||||||
|
- **Success Green** (`#059669`): Success states, positive indicators, confirmations
|
||||||
|
- **Success Light** (`#ECFDF5`): Success background tint
|
||||||
|
- **Warning Amber** (`#D97706`): Warnings, pending states, attention needed
|
||||||
|
- **Warning Light** (`#FFFBEB`): Warning background tint
|
||||||
|
- **Error Red** (`#DC2626`): Errors, destructive actions, required fields
|
||||||
|
- **Error Light** (`#FEF2F2`): Error background tint
|
||||||
|
- **Info Blue** (`#0284C7`): Informational elements, tooltips, help text
|
||||||
|
|
||||||
### Text
|
### Text
|
||||||
- **Plum Black** (`#211922`): Primary text — warm near-black with plum undertone
|
- **Primary Text** (`#0F172A`): Headings, primary body text — deep navy, professional
|
||||||
- **Black** (`#000000`): Secondary text, button text
|
- **Secondary Text** (`#475569`): Descriptions, labels, helper text
|
||||||
- **Olive Gray** (`#62625b`): Secondary descriptions, muted text
|
- **Tertiary Text** (`#94A3B8`): Placeholders, disabled text, timestamps
|
||||||
- **Warm Silver** (`#91918c`): `--comp-button-color-text-transparent-disabled`, disabled text, input borders
|
- **Inverse Text** (`#FFFFFF`): Text on colored/dark surfaces
|
||||||
- **White** (`#ffffff`): Text on dark/colored surfaces
|
|
||||||
|
|
||||||
### Interactive
|
|
||||||
- **Focus Blue** (`#435ee5`): `--comp-button-color-border-focus-outer-transparent`, focus rings
|
|
||||||
- **Performance Purple** (`#6845ab`): `--sema-color-hover-icon-performance-plus`, performance features
|
|
||||||
- **Recommendation Purple** (`#7e238b`): `--sema-color-hover-text-recommendation`, AI recommendation
|
|
||||||
- **Link Blue** (`#2b48d4`): Link text color
|
|
||||||
- **Facebook Blue** (`#0866ff`): `--facebook-background-color`, social login
|
|
||||||
- **Pressed Blue** (`#617bff`): `--base-color-pressed-blue-200`, pressed state
|
|
||||||
|
|
||||||
### Surface & Border
|
### Surface & Border
|
||||||
- **Sand Gray** (`#e5e5e0`): Secondary button background — warm, craft-like
|
- **Page Background** (`#F8FAFC`): App background — cool off-white
|
||||||
- **Warm Light** (`#e0e0d9`): Circular button backgrounds, badges
|
- **Container Background** (`#FFFFFF`): Cards, panels, modals
|
||||||
- **Warm Wash** (`hsla(60, 20%, 98%, 0.5)`): `--comp-badge-color-background-wash-light`, subtle warm badge bg
|
- **Elevated Background** (`#FFFFFF`): Dropdowns, popovers, tooltips
|
||||||
- **Fog** (`#f6f6f3`): Light surface (at 50% opacity)
|
- **Border** (`#E2E8F0`): Default borders, dividers
|
||||||
- **Border Disabled** (`#c8c8c1`): `--sema-color-border-disabled`, disabled borders
|
- **Border Strong** (`#CBD5E1`): Emphasized borders, active card outlines
|
||||||
- **Hover Gray** (`#bcbcb3`): `--base-color-hover-grayscale-150`, hover border
|
- **Hover Background** (`#F1F5F9`): Row hover, item hover backgrounds
|
||||||
- **Dark Surface** (`#33332e`): Dark section backgrounds
|
- **Muted Background** (`#F1F5F9`): Subtle section backgrounds, table headers
|
||||||
|
|
||||||
### Semantic
|
### Dark Mode
|
||||||
- **Error Red** (`#9e0a0a`): Checkbox/form error states
|
- **Dark Page** (`#0F172A`): Dark app background
|
||||||
|
- **Dark Container** (`#1E293B`): Dark cards, panels
|
||||||
|
- **Dark Elevated** (`#334155`): Dark dropdowns, popovers
|
||||||
|
- **Dark Border** (`#334155`): Dark borders
|
||||||
|
- **Dark Hover** (`#1E293B`): Dark row hover
|
||||||
|
- **Dark Text Primary** (`#F8FAFC`): Dark mode primary text
|
||||||
|
- **Dark Text Secondary** (`#94A3B8`): Dark mode secondary text
|
||||||
|
|
||||||
## 3. Typography Rules
|
## 3. Typography Rules
|
||||||
|
|
||||||
### Font Family
|
### Font Family
|
||||||
- **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`
|
- **Primary**: `Noto Sans SC`, fallbacks: `-apple-system, system-ui, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif`
|
||||||
|
- **Monospace (optional)**: `'JetBrains Mono', 'Fira Code', Consolas, monospace`
|
||||||
|
|
||||||
### Hierarchy
|
### Hierarchy
|
||||||
|
|
||||||
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|
| Role | Size | Weight | Line Height | Usage |
|
||||||
|------|------|------|--------|-------------|----------------|-------|
|
|------|------|--------|-------------|-------|
|
||||||
| Display Hero | Pin Sans | 70px (4.38rem) | 600 | normal | normal | Maximum impact |
|
| Page Title | 24px (1.5rem) | 700 | 1.3 | Page headings, dialog titles |
|
||||||
| Section Heading | Pin Sans | 28px (1.75rem) | 700 | normal | -1.2px | Negative tracking |
|
| Section Title | 20px (1.25rem) | 600 | 1.4 | Section headings, card titles |
|
||||||
| Body | Pin Sans | 16px (1.00rem) | 400 | 1.40 | normal | Standard reading |
|
| Subsection | 16px (1rem) | 600 | 1.5 | Subsection labels, form group titles |
|
||||||
| Caption Bold | Pin Sans | 14px (0.88rem) | 700 | normal | normal | Strong metadata |
|
| Body | 14px (0.875rem) | 400 | 1.6 | Standard body text, table cells, descriptions |
|
||||||
| Caption | Pin Sans | 12px (0.75rem) | 400–500 | 1.50 | normal | Small text, tags |
|
| Caption | 12px (0.75rem) | 400 | 1.5 | Timestamps, badges, helper text, metadata |
|
||||||
| Button | Pin Sans | 12px (0.75rem) | 400 | normal | normal | Button labels |
|
|
||||||
|
|
||||||
### Principles
|
### Principles
|
||||||
- **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.
|
- **Chinese-first**: Noto Sans SC ensures excellent CJK rendering
|
||||||
- **Warm weight distribution**: 600–700 for headings, 400–500 for body. No ultra-light weights — the type always feels substantial.
|
- **Readable weights**: 400 for body, 600-700 for headings — always substantial, never thin
|
||||||
- **Negative tracking on headings**: -1.2px on 28px headings creates cozy, intimate section titles.
|
- **Generous line-height**: 1.5-1.6 for body text ensures comfortable reading
|
||||||
- **Single font family**: Pin Sans handles everything — no secondary display or monospace font detected.
|
- **Moderate scale**: 12-24px range creates a compact, professional information hierarchy
|
||||||
|
|
||||||
## 4. Component Stylings
|
## 4. Component Stylings
|
||||||
|
|
||||||
### Buttons
|
### Buttons
|
||||||
|
- **Primary**: Blue (#2563EB) background, white text, 10px radius, soft shadow
|
||||||
**Primary Red**
|
- **Secondary**: White background, slate border (#CBD5E1), dark text, 10px radius
|
||||||
- Background: `#e60023` (Pinterest Red)
|
- **Ghost**: Transparent background, primary blue text, no border
|
||||||
- Text: `#000000` (black — unusual choice for contrast on red)
|
- **Danger**: Red (#DC2626) background, white text, for destructive actions
|
||||||
- Padding: 6px 14px
|
- **All buttons**: Min height 36px, 40px preferred; smooth hover transition 200ms
|
||||||
- Radius: 16px (generously rounded, not pill)
|
|
||||||
- Border: `2px solid rgba(255, 255, 255, 0)` (transparent)
|
|
||||||
- Focus: semantic border + outline via CSS variables
|
|
||||||
|
|
||||||
**Secondary Sand**
|
|
||||||
- Background: `#e5e5e0` (warm sand gray)
|
|
||||||
- Text: `#000000`
|
|
||||||
- Padding: 6px 14px
|
|
||||||
- Radius: 16px
|
|
||||||
- Focus: same semantic border system
|
|
||||||
|
|
||||||
**Circular Action**
|
|
||||||
- Background: `#e0e0d9` (warm light)
|
|
||||||
- Text: `#211922` (plum black)
|
|
||||||
- Radius: 50% (circle)
|
|
||||||
- Use: Pin actions, navigation controls
|
|
||||||
|
|
||||||
**Ghost / Transparent**
|
|
||||||
- Background: transparent
|
|
||||||
- Text: `#000000`
|
|
||||||
- No border
|
|
||||||
- Use: Tertiary actions
|
|
||||||
|
|
||||||
### Cards & Containers
|
### Cards & Containers
|
||||||
- Photography-first pin cards with generous radius (12px–20px)
|
- White background, 12px radius, soft shadow: `0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)`
|
||||||
- No traditional box-shadow on most cards
|
- Hover: Slightly elevated shadow: `0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.05)`
|
||||||
- White or warm fog backgrounds
|
- No thick borders — shadows and subtle background differences create depth
|
||||||
- 8px white thick border on some image containers
|
|
||||||
|
|
||||||
### Inputs
|
### Inputs
|
||||||
- Email input: white background, `1px solid #91918c` border, 16px radius, 11px 15px padding
|
- White background, 1px solid #CBD5E1 border, 10px radius
|
||||||
- Focus: semantic border + outline system via CSS variables
|
- Focus: Blue ring (2px solid #2563EB) with outer glow
|
||||||
|
- Error: Red border + error message below field
|
||||||
|
- Height: 40px standard, 32px small
|
||||||
|
|
||||||
### Navigation
|
### Tables
|
||||||
- Clean header on white or warm background
|
- Header: #F1F5F9 background, #475569 text, 14px weight 600
|
||||||
- Pinterest logo + search bar centered
|
- Row hover: #F1F5F9 background
|
||||||
- Pin Sans 16px for nav links
|
- Cell padding: 16px vertical, 12px horizontal
|
||||||
- Pinterest Red accents for active states
|
- Border: Bottom-only using #E2E8F0
|
||||||
|
|
||||||
### Image Treatment
|
### Navigation / Sidebar
|
||||||
- Pin-style masonry grid (signature Pinterest layout)
|
- Background: White (#FFFFFF) with right border #E2E8F0
|
||||||
- Rounded corners: 12px–20px on images
|
- Active item: #EFF6FF background, #2563EB text, left accent bar (3px)
|
||||||
- Photography as primary content — every pin is an image
|
- Hover item: #F1F5F9 background
|
||||||
- Thick white borders (8px) on featured image containers
|
- Item height: 40px, 12px radius
|
||||||
|
- Icons: 20px, inline with label text
|
||||||
|
|
||||||
|
### Tags / Badges
|
||||||
|
- Small radius (6px), medium padding (4px 8px)
|
||||||
|
- Color-coded backgrounds: blue tint, green tint, amber tint, red tint
|
||||||
|
- Text matches semantic color (darker shade than background)
|
||||||
|
|
||||||
|
### Modals / Dialogs
|
||||||
|
- 16px radius, generous padding (24px)
|
||||||
|
- Soft elevated shadow: `0 8px 30px rgba(0,0,0,0.12)`
|
||||||
|
- Header with title, footer with action buttons
|
||||||
|
|
||||||
## 5. Layout Principles
|
## 5. Layout Principles
|
||||||
|
|
||||||
### Spacing System
|
### Spacing System
|
||||||
- Base unit: 8px
|
- Base unit: 4px
|
||||||
- Scale: 4px, 6px, 7px, 8px, 10px, 11px, 12px, 16px, 18px, 20px, 22px, 24px, 32px, 80px, 100px
|
- Scale: 4px, 8px, 12px, 16px, 20px, 24px, 32px, 40px, 48px, 64px
|
||||||
- Large jumps: 32px → 80px → 100px for section spacing
|
- Content padding: 24px standard, 16px compact
|
||||||
|
|
||||||
### Grid & Container
|
### Layout
|
||||||
- Masonry grid for pin content (signature layout)
|
- Fixed sidebar: 240px wide, collapsible to 72px
|
||||||
- Centered content sections with generous max-width
|
- Sticky header: 56px
|
||||||
- Full-width dark footer
|
- Content area: Fluid width with max comfortable reading width
|
||||||
- Search bar as primary navigation element
|
- Standard CRUD table/list views for data management
|
||||||
|
|
||||||
### Whitespace Philosophy
|
|
||||||
- **Inspiration density**: The masonry grid packs pins tightly — the content density IS the value proposition. Whitespace exists between sections, not within the grid.
|
|
||||||
- **Breathing above, density below**: Hero/feature sections get generous padding; the pin grid is compact and immersive.
|
|
||||||
|
|
||||||
### Border Radius Scale
|
### Border Radius Scale
|
||||||
- Standard (12px): Small cards, links
|
- Small (6px): Tags, badges, small elements
|
||||||
- Button (16px): Buttons, inputs, medium cards
|
- Standard (10px): Buttons, inputs, cards
|
||||||
- Comfortable (20px): Feature cards
|
- Large (12px): Panels, modals, large containers
|
||||||
- Large (28px): Large containers
|
- Full (50%): Circular avatars, icon buttons
|
||||||
- Section (32px): Tab elements, large panels
|
|
||||||
- Hero (40px): Hero containers, large feature blocks
|
|
||||||
- Circle (50%): Action buttons, tab indicators
|
|
||||||
|
|
||||||
## 6. Depth & Elevation
|
## 6. Depth & Elevation
|
||||||
|
|
||||||
| Level | Treatment | Use |
|
| Level | Shadow | Use |
|
||||||
|-------|-----------|-----|
|
|-------|--------|-----|
|
||||||
| Flat (Level 0) | No shadow | Default — pins rely on content, not shadow |
|
| Level 0 (Flat) | None | Page background, sidebar |
|
||||||
| Subtle (Level 1) | Minimal shadow (from tokens) | Elevated overlays, dropdowns |
|
| Level 1 (Subtle) | `0 1px 2px rgba(0,0,0,0.05)` | Cards, form sections |
|
||||||
| Focus (Accessibility) | `--sema-color-border-focus-outer-default` ring | Focus states |
|
| Level 2 (Default) | `0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)` | Elevated cards, dropdowns |
|
||||||
|
| Level 3 (Elevated) | `0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.05)` | Hover states, active cards |
|
||||||
|
| Level 4 (Modal) | `0 8px 30px rgba(0,0,0,0.12)` | Modals, overlays |
|
||||||
|
|
||||||
**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.
|
## 7. Interactive States
|
||||||
|
|
||||||
## 7. Do's and Don'ts
|
### Hover
|
||||||
|
- Background color shift: `#F1F5F9` for neutral, semantic tint for colored elements
|
||||||
|
- Transition: 200ms ease
|
||||||
|
- Cursor: `pointer` on clickable elements
|
||||||
|
|
||||||
|
### Focus
|
||||||
|
- 2px solid ring using `#2563EB`
|
||||||
|
- 2px offset for visibility
|
||||||
|
- Always visible for keyboard navigation
|
||||||
|
|
||||||
|
### Active / Pressed
|
||||||
|
- Slightly darker shade of the element's color
|
||||||
|
- Brief scale or shadow change for tactile feedback
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
- Reduced opacity (0.5)
|
||||||
|
- No hover effects
|
||||||
|
- Cursor: `not-allowed`
|
||||||
|
|
||||||
|
### Loading
|
||||||
|
- Spinner or skeleton placeholder
|
||||||
|
- Disabled interactions during async operations
|
||||||
|
|
||||||
|
## 8. Do's and Don'ts
|
||||||
|
|
||||||
### Do
|
### Do
|
||||||
- Use warm neutrals (`#e5e5e0`, `#e0e0d9`, `#91918c`) — the warm olive/sand tone is the identity
|
- Use soft shadows for depth — the Soft UI Evolution identity
|
||||||
- Apply Pinterest Red (`#e60023`) only for primary CTAs — it's bold and singular
|
- Apply Primary Blue (#2563EB) only for primary actions and active states
|
||||||
- Use Pin Sans exclusively — one font for everything
|
- Use Noto Sans SC for consistent Chinese rendering
|
||||||
- Apply generous border-radius: 16px for buttons/inputs, 20px+ for cards
|
- Apply 10-12px radius — friendly but professional
|
||||||
- Keep the masonry grid dense — content density is the value
|
- Use semantic color tints for status backgrounds
|
||||||
- Use warm badge backgrounds (`hsla(60,20%,98%,.5)`) for subtle warm washes
|
- Keep tables clean with subtle header differentiation
|
||||||
- Use `#211922` (plum black) for primary text — it's warmer than pure black
|
- Ensure 4.5:1 contrast ratio for all text
|
||||||
|
|
||||||
### Don't
|
### Don't
|
||||||
- Don't use cool gray neutrals — always warm/olive-toned
|
- Don't use heavy or dramatic shadows — keep depth subtle
|
||||||
- Don't use pure black (`#000000`) as primary text — use plum black (`#211922`)
|
- Don't use pure black (#000000) for text — use #0F172A instead
|
||||||
- Don't use pill-shaped buttons — 16px radius is rounded but not pill
|
- Don't use pill-shaped buttons — 10px radius is rounded but not pill
|
||||||
- Don't add heavy shadows — Pinterest is flat by design, depth from content
|
- Don't mix warm and cool neutrals — stay in the slate family
|
||||||
- Don't use small border-radius (<12px) on cards — the generous rounding is core
|
- Don't use emojis as icons — use SVG icons (Lucide/Heroicons)
|
||||||
- Don't introduce additional brand colors — red + warm neutrals is the complete palette
|
- Don't use decorative-only animations — every animation must serve a purpose
|
||||||
- Don't use thin font weights — Pin Sans at 400 minimum
|
- Don't use colors as the sole means of conveying information
|
||||||
|
|
||||||
## 8. Responsive Behavior
|
## 9. Responsive Behavior
|
||||||
|
|
||||||
### Breakpoints
|
### Breakpoints
|
||||||
| Name | Width | Key Changes |
|
| Name | Width | Key Changes |
|
||||||
|------|-------|-------------|
|
|------|-------|-------------|
|
||||||
| Mobile | <576px | Single column, compact layout |
|
| Mobile | <576px | Sidebar collapsed, single column |
|
||||||
| Mobile Large | 576–768px | 2-column pin grid |
|
| Tablet | 576-768px | Sidebar collapsed, 2-column grid |
|
||||||
| Tablet | 768–890px | Expanded grid |
|
| Desktop Small | 768-1024px | Sidebar expanded, responsive grid |
|
||||||
| Desktop Small | 890–1312px | Standard masonry grid |
|
| Desktop | 1024-1440px | Full layout |
|
||||||
| Desktop | 1312–1440px | Full layout |
|
| Large Desktop | >1440px | Maximum content width |
|
||||||
| Large Desktop | 1440–1680px | Expanded grid columns |
|
|
||||||
| Ultra-wide | >1680px | Maximum grid density |
|
|
||||||
|
|
||||||
### Collapsing Strategy
|
### Collapsing Strategy
|
||||||
- Pin grid: 5+ columns → 3 → 2 → 1
|
- Sidebar: 240px → 72px (icon only) on mobile/tablet
|
||||||
- Navigation: search bar + icons → simplified mobile nav
|
- Tables: Horizontal scroll on narrow screens
|
||||||
- Feature sections: side-by-side → stacked
|
- Forms: Single column on mobile, multi-column on desktop
|
||||||
- Hero: 70px → scales down proportionally
|
- Cards: Full-width stack → grid layout
|
||||||
- Footer: dark multi-column → stacked
|
|
||||||
|
|
||||||
## 9. Agent Prompt Guide
|
|
||||||
|
|
||||||
### Quick Color Reference
|
|
||||||
- Brand: Pinterest Red (`#e60023`)
|
|
||||||
- Background: White (`#ffffff`)
|
|
||||||
- Text: Plum Black (`#211922`)
|
|
||||||
- Secondary text: Olive Gray (`#62625b`)
|
|
||||||
- Button surface: Sand Gray (`#e5e5e0`)
|
|
||||||
- Border: Warm Silver (`#91918c`)
|
|
||||||
- Focus: Focus Blue (`#435ee5`)
|
|
||||||
|
|
||||||
### Example Component Prompts
|
|
||||||
- "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 pin card: white background, 16px radius, no shadow. Photography fills top, 16px Pin Sans weight 400 description below in #62625b."
|
|
||||||
- "Build a circular action button: #e0e0d9 background, 50% radius, #211922 icon."
|
|
||||||
- "Create an input field: white background, 1px solid #91918c, 16px radius, 11px 15px padding. Focus: blue outline via semantic tokens."
|
|
||||||
- "Design the dark footer: #33332e background. Pinterest script logo in white. 12px Pin Sans links in #91918c."
|
|
||||||
|
|
||||||
### Iteration Guide
|
|
||||||
1. Warm neutrals everywhere — olive/sand grays, never cool steel
|
|
||||||
2. Pinterest Red for CTAs only — bold and singular
|
|
||||||
3. 16px radius on buttons/inputs, 20px+ on cards — generous but not pill
|
|
||||||
4. Pin Sans is the only font — compact at 12px for UI, 70px for display
|
|
||||||
5. Photography carries the design — the UI stays warm and minimal
|
|
||||||
6. Plum black (#211922) for text — warmer than pure black
|
|
||||||
|
|
||||||
## 10. ERP Platform Adaptations
|
## 10. ERP Platform Adaptations
|
||||||
|
|
||||||
Pinterest's design system is adapted for an enterprise resource planning platform:
|
|
||||||
|
|
||||||
### Layout
|
### Layout
|
||||||
- Fixed sidebar navigation (240px wide, collapsible to 72px) replaces Pinterest's top nav
|
- Fixed sidebar navigation (240px wide, collapsible to 72px)
|
||||||
- Sticky header (56px) with search, notifications, user menu
|
- Sticky header (56px) with search, notifications, user menu
|
||||||
- Content area uses CSS Grid for responsive multi-column dashboards
|
- Content area uses CSS Grid for responsive multi-column dashboards
|
||||||
- Standard CRUD table/list views replace masonry grid for data management
|
- Standard CRUD table/list views replace masonry grid for data management
|
||||||
|
|
||||||
### Color Adaptations
|
### Color Adaptations
|
||||||
- Pinterest Red (`#e60023`) for primary actions (Save, Create, Submit)
|
- Primary Blue (#2563EB) for primary actions (Save, Create, Submit)
|
||||||
- Sand Gray (`#e5e5e0`) for secondary/outlined buttons
|
- Success Green (#059669) for positive states (Approved, Completed, Paid)
|
||||||
- Focus Blue (`#435ee5`) for informational elements and links
|
- Warning Amber (#D97706) for pending states (Pending Review, Awaiting)
|
||||||
- Pinterest Green (`#103c25`) for success states
|
- Error Red (#DC2626) for destructive/error states (Rejected, Failed, Overdue)
|
||||||
- Pinterest Error (`#9e0a0a`) for destructive/error states
|
- Info Blue (#0284C7) for informational elements (Tips, Help, Documentation)
|
||||||
|
|
||||||
### Component Adaptations
|
### Component Adaptations
|
||||||
- **Tables**: Warm header bg (`#f6f6f3`), generous cell padding, red-tinted row hover
|
- **Tables**: Slate header bg (#F1F5F9), generous cell padding, subtle hover (#F1F5F9)
|
||||||
- **Forms**: 16px radius inputs, warm borders (`#91918c`), generous spacing
|
- **Forms**: 10px radius inputs, slate borders (#CBD5E1), generous spacing
|
||||||
- **Cards**: 20px radius, minimal shadow, warm fog hover backgrounds
|
- **Cards**: 12px radius, soft shadow, white background
|
||||||
- **Sidebar**: Plum black active states, warm sand hover, 12px radius items
|
- **Sidebar**: Blue active states (#EFF6FF bg), slate hover, 12px radius items
|
||||||
- **Tags/Badges**: Warm pill shapes (8px radius), sand backgrounds
|
- **Tags/Badges**: 6px radius, semantic color tints (blue/green/amber/red)
|
||||||
- **Modals**: 28px radius, warm shadows, generous padding
|
- **Modals**: 16px radius, elevated shadow, generous padding
|
||||||
|
|
||||||
### Dark Mode
|
### Dark Mode
|
||||||
- Background: `#1a1a18` (warm dark)
|
- Background: #0F172A (deep navy)
|
||||||
- Container: `#2a2a28` (olive dark)
|
- Container: #1E293B (dark slate)
|
||||||
- Elevated: `#33332e` (plum dark)
|
- Elevated: #334155 (medium slate)
|
||||||
- Sidebar: `#211922` (plum black)
|
- Border: #334155
|
||||||
- Active accent: `#f05a5a` (warm light red)
|
- Active accent: #3B82F6 (lighter blue for dark backgrounds)
|
||||||
- Text: Standard white/gray hierarchy with olive undertones
|
- Text: #F8FAFC primary, #94A3B8 secondary
|
||||||
|
|
||||||
|
## 11. Agent Prompt Guide
|
||||||
|
|
||||||
|
### Quick Color Reference
|
||||||
|
- Brand: Primary Blue (#2563EB)
|
||||||
|
- Background: Cool Off-White (#F8FAFC)
|
||||||
|
- Text: Deep Navy (#0F172A)
|
||||||
|
- Secondary text: Slate (#475569)
|
||||||
|
- Border: Light Slate (#E2E8F0)
|
||||||
|
- Success: Green (#059669)
|
||||||
|
- Warning: Amber (#D97706)
|
||||||
|
- Error: Red (#DC2626)
|
||||||
|
- Focus: Blue (#2563EB)
|
||||||
|
|
||||||
|
### Example Component Prompts
|
||||||
|
- "Create a card: white background, 12px radius, soft shadow (0 1px 3px rgba(0,0,0,0.06)). Title in 16px weight 600 #0F172A. Body in 14px #475569."
|
||||||
|
- "Design a primary button: #2563EB background, white text, 10px radius, 8px 16px padding. Hover: #1D4ED8. Focus: 2px solid #2563EB ring."
|
||||||
|
- "Build a table: header #F1F5F9 background, #475569 text 14px weight 600. Row hover #F1F5F9. Cell padding 16px 12px. Border bottom #E2E8F0."
|
||||||
|
- "Create a sidebar: white background, right border #E2E8F0. Active item: #EFF6FF background, #2563EB text, 3px left accent bar. Hover: #F1F5F9."
|
||||||
|
- "Design a modal: 16px radius, shadow 0 8px 30px rgba(0,0,0,0.12). Title 20px weight 600. Footer with primary blue CTA."
|
||||||
|
|
||||||
|
### Iteration Guide
|
||||||
|
1. Soft shadows everywhere — subtle depth is the identity
|
||||||
|
2. Primary Blue for CTAs and active states — trustworthy, not overwhelming
|
||||||
|
3. 10px radius on buttons/inputs, 12px on cards — friendly but professional
|
||||||
|
4. Noto Sans SC is the primary font — Chinese-first, readable at all sizes
|
||||||
|
5. Slate neutrals — never pure black or pure gray, always with blue undertone
|
||||||
|
6. Semantic tints for status — green/amber/red backgrounds with matching text
|
||||||
|
|||||||
@@ -31,27 +31,27 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const themeConfig = {
|
const themeConfig = {
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: '#e60023',
|
colorPrimary: '#2563eb',
|
||||||
colorSuccess: '#103c25',
|
colorSuccess: '#059669',
|
||||||
colorWarning: '#b56e1a',
|
colorWarning: '#d97706',
|
||||||
colorError: '#9e0a0a',
|
colorError: '#dc2626',
|
||||||
colorInfo: '#435ee5',
|
colorInfo: '#0284c7',
|
||||||
colorBgLayout: '#f6f6f3',
|
colorBgLayout: '#f8fafc',
|
||||||
colorBgContainer: '#ffffff',
|
colorBgContainer: '#ffffff',
|
||||||
colorBgElevated: '#ffffff',
|
colorBgElevated: '#ffffff',
|
||||||
colorBorder: '#e5e5e0',
|
colorBorder: '#e2e8f0',
|
||||||
colorBorderSecondary: '#e0e0d9',
|
colorBorderSecondary: '#f1f5f9',
|
||||||
borderRadius: 16,
|
borderRadius: 10,
|
||||||
borderRadiusLG: 20,
|
borderRadiusLG: 12,
|
||||||
borderRadiusSM: 8,
|
borderRadiusSM: 6,
|
||||||
fontFamily: "-apple-system, system-ui, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', Helvetica, Arial, sans-serif",
|
fontFamily: "'Noto Sans SC', -apple-system, system-ui, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', Helvetica, Arial, sans-serif",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontSizeHeading4: 20,
|
fontSizeHeading4: 20,
|
||||||
controlHeight: 40,
|
controlHeight: 40,
|
||||||
controlHeightLG: 44,
|
controlHeightLG: 44,
|
||||||
controlHeightSM: 32,
|
controlHeightSM: 32,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
boxShadowSecondary: '0 2px 8px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04)',
|
boxShadowSecondary: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Button: {
|
Button: {
|
||||||
@@ -62,21 +62,21 @@ const themeConfig = {
|
|||||||
paddingLG: 20,
|
paddingLG: 20,
|
||||||
},
|
},
|
||||||
Table: {
|
Table: {
|
||||||
headerBg: '#fafaf8',
|
headerBg: '#f1f5f9',
|
||||||
headerColor: '#62625b',
|
headerColor: '#475569',
|
||||||
rowHoverBg: '#fef0f0',
|
rowHoverBg: '#f1f5f9',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
itemBorderRadius: 12,
|
itemBorderRadius: 10,
|
||||||
itemMarginInline: 8,
|
itemMarginInline: 8,
|
||||||
itemHeight: 40,
|
itemHeight: 40,
|
||||||
},
|
},
|
||||||
Modal: {
|
Modal: {
|
||||||
borderRadiusLG: 28,
|
borderRadiusLG: 16,
|
||||||
},
|
},
|
||||||
Tag: {
|
Tag: {
|
||||||
borderRadiusSM: 8,
|
borderRadiusSM: 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -85,20 +85,20 @@ const darkThemeConfig = {
|
|||||||
...themeConfig,
|
...themeConfig,
|
||||||
token: {
|
token: {
|
||||||
...themeConfig.token,
|
...themeConfig.token,
|
||||||
colorBgLayout: '#1a1a18',
|
colorBgLayout: '#0f172a',
|
||||||
colorBgContainer: '#2a2a28',
|
colorBgContainer: '#1e293b',
|
||||||
colorBgElevated: '#33332e',
|
colorBgElevated: '#334155',
|
||||||
colorBorder: 'rgba(255, 255, 255, 0.08)',
|
colorBorder: '#334155',
|
||||||
colorBorderSecondary: 'rgba(255, 255, 255, 0.06)',
|
colorBorderSecondary: 'rgba(255, 255, 255, 0.06)',
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
boxShadowSecondary: '0 4px 16px rgba(0,0,0,0.2), 0 2px 6px rgba(0,0,0,0.15)',
|
boxShadowSecondary: '0 2px 8px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2)',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
...themeConfig.components,
|
...themeConfig.components,
|
||||||
Table: {
|
Table: {
|
||||||
headerBg: '#33332e',
|
headerBg: '#1e293b',
|
||||||
headerColor: '#91918c',
|
headerColor: '#94a3b8',
|
||||||
rowHoverBg: '#33332e',
|
rowHoverBg: '#1e293b',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function NotificationPanel() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
style={{ fontSize: 12, color: '#e60023' }}
|
style={{ fontSize: 12, color: '#2563eb' }}
|
||||||
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 ? '#211922' : '#fef0f0') : 'transparent',
|
background: !item.is_read ? (isDark ? '#0f172a' : '#eff6ff') : '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 ? '#211922' : '#fafaf8';
|
e.currentTarget.style.background = isDark ? '#0f172a' : '#f1f5f9';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
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: '#e60023',
|
background: '#2563eb',
|
||||||
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 ? '#211922' : '#f6f6f3'}`,
|
borderTop: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
}}>
|
}}>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
onClick={() => navigate('/messages')}
|
onClick={() => navigate('/messages')}
|
||||||
style={{ fontSize: 13, color: '#e60023', fontWeight: 500 }}
|
style={{ fontSize: 13, color: '#2563eb', 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 ? '#211922' : '#f6f6f3';
|
e.currentTarget.style.background = isDark ? '#0f172a' : '#f8fafc';
|
||||||
}}
|
}}
|
||||||
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 ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
}} />
|
}} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,65 +2,66 @@
|
|||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
* ERP Platform — Design System Tokens & Global Styles
|
* ERP Platform — Design System Tokens & Global Styles
|
||||||
* Inspired by Pinterest: warm discovery, red accent, generous radius
|
* Soft UI Evolution: Professional, warm, accessible for all industries
|
||||||
|
* Generated by UI UX Pro Max
|
||||||
* ==================================================================== */
|
* ==================================================================== */
|
||||||
|
|
||||||
/* --- Design Tokens (CSS Custom Properties) --- */
|
/* --- Design Tokens (CSS Custom Properties) --- */
|
||||||
:root {
|
:root {
|
||||||
/* Primary Palette — Pinterest Red */
|
/* Primary Palette — Trust Blue */
|
||||||
--erp-primary: #e60023;
|
--erp-primary: #2563eb;
|
||||||
--erp-primary-hover: #ad081b;
|
--erp-primary-hover: #1d4ed8;
|
||||||
--erp-primary-active: #9e0a0a;
|
--erp-primary-active: #1e40af;
|
||||||
--erp-primary-light: #fef0f0;
|
--erp-primary-light: #eff6ff;
|
||||||
--erp-primary-light-hover: #fddbdb;
|
--erp-primary-light-hover: #dbeafe;
|
||||||
--erp-primary-bg-subtle: #fef0f0;
|
--erp-primary-bg-subtle: #eff6ff;
|
||||||
|
|
||||||
/* Semantic Colors — Pinterest warm tones */
|
/* Semantic Colors — Professional slate tones */
|
||||||
--erp-success: #103c25;
|
--erp-success: #059669;
|
||||||
--erp-success-bg: #ecfdf5;
|
--erp-success-bg: #ecfdf5;
|
||||||
--erp-warning: #b56e1a;
|
--erp-warning: #d97706;
|
||||||
--erp-warning-bg: #fff7ed;
|
--erp-warning-bg: #fffbeb;
|
||||||
--erp-error: #9e0a0a;
|
--erp-error: #dc2626;
|
||||||
--erp-error-bg: #fef2f2;
|
--erp-error-bg: #fef2f2;
|
||||||
--erp-info: #435ee5;
|
--erp-info: #0284c7;
|
||||||
--erp-info-bg: #eef1fd;
|
--erp-info-bg: #f0f9ff;
|
||||||
|
|
||||||
/* Neutral Palette — Warm neutrals with olive/sand undertones */
|
/* Neutral Palette — Slate neutrals with blue undertones */
|
||||||
--erp-bg-page: #f6f6f3;
|
--erp-bg-page: #f8fafc;
|
||||||
--erp-bg-container: #ffffff;
|
--erp-bg-container: #ffffff;
|
||||||
--erp-bg-elevated: #ffffff;
|
--erp-bg-elevated: #ffffff;
|
||||||
--erp-bg-spotlight: #fafaf8;
|
--erp-bg-spotlight: #f1f5f9;
|
||||||
--erp-bg-sidebar: #ffffff;
|
--erp-bg-sidebar: #ffffff;
|
||||||
--erp-bg-sidebar-hover: #f6f6f3;
|
--erp-bg-sidebar-hover: #f1f5f9;
|
||||||
--erp-bg-sidebar-active: #fef0f0;
|
--erp-bg-sidebar-active: #eff6ff;
|
||||||
|
|
||||||
/* Text Colors — Warm near-black */
|
/* Text Colors — Deep navy */
|
||||||
--erp-text-primary: #211922;
|
--erp-text-primary: #0f172a;
|
||||||
--erp-text-secondary: #62625b;
|
--erp-text-secondary: #475569;
|
||||||
--erp-text-tertiary: #91918c;
|
--erp-text-tertiary: #94a3b8;
|
||||||
--erp-text-inverse: #ffffff;
|
--erp-text-inverse: #ffffff;
|
||||||
--erp-text-sidebar: #62625b;
|
--erp-text-sidebar: #475569;
|
||||||
--erp-text-sidebar-active: #e60023;
|
--erp-text-sidebar-active: #2563eb;
|
||||||
|
|
||||||
/* Border Colors — Whisper borders */
|
/* Border Colors — Slate borders */
|
||||||
--erp-border: #e5e5e0;
|
--erp-border: #e2e8f0;
|
||||||
--erp-border-light: #e0e0d9;
|
--erp-border-light: #f1f5f9;
|
||||||
--erp-border-dark: #c8c8c1;
|
--erp-border-dark: #cbd5e1;
|
||||||
|
|
||||||
/* Shadows — Multi-layer, ultra-subtle */
|
/* Shadows — Soft UI Evolution: subtle, layered depth */
|
||||||
--erp-shadow-xs: 0 1px 2px rgba(0,0,0,0.03);
|
--erp-shadow-xs: 0 1px 2px rgba(0,0,0,0.05);
|
||||||
--erp-shadow-sm: 0 2px 8px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04);
|
--erp-shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
|
||||||
--erp-shadow-md: 0 4px 16px rgba(0,0,0,0.07), 0 2px 6px rgba(0,0,0,0.04);
|
--erp-shadow-md: 0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.05);
|
||||||
--erp-shadow-lg: 0 8px 24px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.05);
|
--erp-shadow-lg: 0 8px 30px rgba(0,0,0,0.12);
|
||||||
--erp-shadow-xl: 0 12px 36px rgba(0,0,0,0.1), 0 6px 18px rgba(0,0,0,0.06);
|
--erp-shadow-xl: 0 12px 40px rgba(0,0,0,0.15);
|
||||||
|
|
||||||
/* Radius — Notion subtle roundness */
|
/* Radius — Soft UI: friendly but professional */
|
||||||
--erp-radius-sm: 8px;
|
--erp-radius-sm: 6px;
|
||||||
--erp-radius-md: 16px;
|
--erp-radius-md: 10px;
|
||||||
--erp-radius-lg: 20px;
|
--erp-radius-lg: 12px;
|
||||||
--erp-radius-xl: 28px;
|
--erp-radius-xl: 16px;
|
||||||
|
|
||||||
/* Spacing — 8px base unit */
|
/* Spacing — 4px base unit */
|
||||||
--erp-space-xs: 4px;
|
--erp-space-xs: 4px;
|
||||||
--erp-space-sm: 8px;
|
--erp-space-sm: 8px;
|
||||||
--erp-space-md: 16px;
|
--erp-space-md: 16px;
|
||||||
@@ -68,11 +69,10 @@
|
|||||||
--erp-space-xl: 32px;
|
--erp-space-xl: 32px;
|
||||||
--erp-space-2xl: 48px;
|
--erp-space-2xl: 48px;
|
||||||
|
|
||||||
/* Typography — Inter as primary */
|
/* Typography — Noto Sans SC for Chinese-first ERP */
|
||||||
--erp-font-family: 'Inter', -apple-system, system-ui, 'Segoe UI', 'PingFang SC',
|
--erp-font-family: 'Noto Sans SC', -apple-system, system-ui, 'Segoe UI', Roboto,
|
||||||
'Microsoft YaHei', 'Hiragino Sans GB', Helvetica, Arial, sans-serif;
|
'PingFang SC', 'Microsoft YaHei', Helvetica, Arial, sans-serif;
|
||||||
--erp-font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', Menlo, Monaco, Consolas,
|
--erp-font-mono: 'JetBrains Mono', 'Fira Code', Consolas, Monaco, monospace;
|
||||||
'Liberation Mono', 'Courier New', monospace;
|
|
||||||
--erp-font-size-xs: 12px;
|
--erp-font-size-xs: 12px;
|
||||||
--erp-font-size-sm: 13px;
|
--erp-font-size-sm: 13px;
|
||||||
--erp-font-size-base: 14px;
|
--erp-font-size-base: 14px;
|
||||||
@@ -86,10 +86,10 @@
|
|||||||
--erp-transition-base: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
--erp-transition-base: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--erp-transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
--erp-transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
/* Trend Colors — Warm */
|
/* Trend Colors */
|
||||||
--erp-trend-up: #103c25;
|
--erp-trend-up: #059669;
|
||||||
--erp-trend-down: #9e0a0a;
|
--erp-trend-down: #dc2626;
|
||||||
--erp-trend-neutral: #62625b;
|
--erp-trend-neutral: #475569;
|
||||||
|
|
||||||
/* Line Height */
|
/* Line Height */
|
||||||
--erp-line-height-tight: 1.25;
|
--erp-line-height-tight: 1.25;
|
||||||
@@ -102,48 +102,48 @@
|
|||||||
--erp-header-height: 56px;
|
--erp-header-height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Dark Mode Tokens — Warm dark, Notion-inspired --- */
|
/* --- Dark Mode Tokens --- */
|
||||||
[data-theme='dark'] {
|
[data-theme='dark'] {
|
||||||
--erp-primary-light: rgba(230, 0, 35, 0.15);
|
--erp-primary-light: rgba(37, 99, 235, 0.15);
|
||||||
--erp-primary-light-hover: rgba(230, 0, 35, 0.22);
|
--erp-primary-light-hover: rgba(37, 99, 235, 0.22);
|
||||||
--erp-primary-bg-subtle: rgba(230, 0, 35, 0.1);
|
--erp-primary-bg-subtle: rgba(37, 99, 235, 0.1);
|
||||||
|
|
||||||
--erp-bg-page: #1a1a18;
|
--erp-bg-page: #0f172a;
|
||||||
--erp-bg-container: #2a2a28;
|
--erp-bg-container: #1e293b;
|
||||||
--erp-bg-elevated: #33332e;
|
--erp-bg-elevated: #334155;
|
||||||
--erp-bg-spotlight: #33332e;
|
--erp-bg-spotlight: #1e293b;
|
||||||
--erp-bg-sidebar: #211922;
|
--erp-bg-sidebar: #0f172a;
|
||||||
--erp-bg-sidebar-hover: #33332e;
|
--erp-bg-sidebar-hover: #1e293b;
|
||||||
|
|
||||||
--erp-text-primary: rgba(255, 255, 255, 0.95);
|
--erp-text-primary: rgba(255, 255, 255, 0.95);
|
||||||
--erp-text-secondary: #91918c;
|
--erp-text-secondary: #94a3b8;
|
||||||
--erp-text-tertiary: #62625b;
|
--erp-text-tertiary: #64748b;
|
||||||
--erp-text-sidebar: #91918c;
|
--erp-text-sidebar: #94a3b8;
|
||||||
--erp-text-sidebar-active: #f05a5a;
|
--erp-text-sidebar-active: #60a5fa;
|
||||||
|
|
||||||
--erp-border: rgba(255, 255, 255, 0.08);
|
--erp-border: #334155;
|
||||||
--erp-border-light: rgba(255, 255, 255, 0.05);
|
--erp-border-light: rgba(255, 255, 255, 0.06);
|
||||||
--erp-border-dark: rgba(255, 255, 255, 0.12);
|
--erp-border-dark: rgba(255, 255, 255, 0.12);
|
||||||
|
|
||||||
--erp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
--erp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||||
--erp-shadow-sm: rgba(0, 0, 0, 0.2) 0px 4px 18px, rgba(0, 0, 0, 0.15) 0px 2px 8px;
|
--erp-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
--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: 0 4px 12px rgba(0, 0, 0, 0.3), 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||||
--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: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
--erp-trend-up: #3db377;
|
--erp-trend-up: #34d399;
|
||||||
--erp-trend-down: #9e0a0a;
|
--erp-trend-down: #f87171;
|
||||||
--erp-trend-neutral: #91918c;
|
--erp-trend-neutral: #94a3b8;
|
||||||
|
|
||||||
--erp-success-bg: rgba(16, 60, 37, 0.15);
|
--erp-success-bg: rgba(5, 150, 105, 0.15);
|
||||||
--erp-warning-bg: rgba(181, 110, 26, 0.15);
|
--erp-warning-bg: rgba(217, 119, 6, 0.15);
|
||||||
--erp-error-bg: rgba(158, 10, 10, 0.15);
|
--erp-error-bg: rgba(220, 38, 38, 0.15);
|
||||||
--erp-info-bg: rgba(67, 94, 229, 0.15);
|
--erp-info-bg: rgba(2, 132, 199, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-stat-card-trend-up { color: #2a9d99; }
|
[data-theme='dark'] .erp-stat-card-trend-up { color: #34d399; }
|
||||||
[data-theme='dark'] .erp-stat-card-trend-down { color: #e5534b; }
|
[data-theme='dark'] .erp-stat-card-trend-down { color: #f87171; }
|
||||||
[data-theme='dark'] .erp-stat-card-trend-neutral { color: #91918c; }
|
[data-theme='dark'] .erp-stat-card-trend-neutral { color: #94a3b8; }
|
||||||
[data-theme='dark'] .erp-stat-card-trend-label { color: #91918c; }
|
[data-theme='dark'] .erp-stat-card-trend-label { color: #94a3b8; }
|
||||||
|
|
||||||
/* --- Global Reset & Base --- */
|
/* --- Global Reset & Base --- */
|
||||||
body {
|
body {
|
||||||
@@ -182,7 +182,7 @@ body {
|
|||||||
|
|
||||||
/* --- Selection --- */
|
/* --- Selection --- */
|
||||||
::selection {
|
::selection {
|
||||||
background-color: rgba(230, 0, 35, 0.15);
|
background-color: rgba(37, 99, 235, 0.15);
|
||||||
color: var(--erp-text-primary);
|
color: var(--erp-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,11 +190,11 @@ body {
|
|||||||
* Component Overrides — Ant Design Enhancement
|
* Component Overrides — Ant Design Enhancement
|
||||||
* ==================================================================== */
|
* ==================================================================== */
|
||||||
|
|
||||||
/* --- Card — Whisper border, soft shadow --- */
|
/* --- Card — Soft shadow, clean border --- */
|
||||||
.ant-card {
|
.ant-card {
|
||||||
border-radius: var(--erp-radius-lg) !important;
|
border-radius: var(--erp-radius-lg) !important;
|
||||||
border: 1px solid var(--erp-border) !important;
|
border: 1px solid var(--erp-border) !important;
|
||||||
box-shadow: none !important;
|
box-shadow: var(--erp-shadow-xs) !important;
|
||||||
transition: box-shadow var(--erp-transition-base) !important;
|
transition: box-shadow var(--erp-transition-base) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-tbody > tr:hover > td {
|
.ant-table-tbody > tr:hover > td {
|
||||||
background: var(--erp-primary-bg-subtle) !important;
|
background: var(--erp-bg-spotlight) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-tbody > tr > td {
|
.ant-table-tbody > tr > td {
|
||||||
@@ -270,9 +270,9 @@ body {
|
|||||||
border-bottom: 1px solid var(--erp-border-light) !important;
|
border-bottom: 1px solid var(--erp-border-light) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Button — Subtle 4px radius, no heavy shadow --- */
|
/* --- Button --- */
|
||||||
.ant-btn-primary {
|
.ant-btn-primary {
|
||||||
border-radius: 16px !important;
|
border-radius: var(--erp-radius-md) !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,16 +283,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-default {
|
.ant-btn-default {
|
||||||
border-radius: 16px !important;
|
border-radius: var(--erp-radius-md) !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Input — 4px radius, whisper border --- */
|
/* --- Input --- */
|
||||||
.ant-input,
|
.ant-input,
|
||||||
.ant-input-affix-wrapper,
|
.ant-input-affix-wrapper,
|
||||||
.ant-select-selector,
|
.ant-select-selector,
|
||||||
.ant-picker {
|
.ant-picker {
|
||||||
border-radius: 16px !important;
|
border-radius: var(--erp-radius-md) !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(230, 0, 35, 0.12) !important;
|
box-shadow: 0 0 0 2px rgba(37, 99, 235, 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, #e60023, #f05a5a); }
|
.erp-gradient-card.indigo::before { background: linear-gradient(90deg, #2563eb, #60a5fa); }
|
||||||
.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #103c25, #3db377); }
|
.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #059669, #34d399); }
|
||||||
.erp-gradient-card.amber::before { background: linear-gradient(90deg, #b56e1a, #fbbf24); }
|
.erp-gradient-card.amber::before { background: linear-gradient(90deg, #d97706, #fbbf24); }
|
||||||
.erp-gradient-card.rose::before { background: linear-gradient(90deg, #9e0a0a, #f05a5a); }
|
.erp-gradient-card.rose::before { background: linear-gradient(90deg, #dc2626, #f87171); }
|
||||||
.erp-gradient-card.sky::before { background: linear-gradient(90deg, #435ee5, #8fa4f0); }
|
.erp-gradient-card.sky::before { background: linear-gradient(90deg, #0284c7, #38bdf8); }
|
||||||
.erp-gradient-card.violet::before { background: linear-gradient(90deg, #6845ab, #a78bfa); }
|
.erp-gradient-card.violet::before { background: linear-gradient(90deg, #7c3aed, #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: 16px;
|
border-radius: var(--erp-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 16px 16px;
|
border-radius: 0 0 var(--erp-radius-md) var(--erp-radius-md);
|
||||||
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: 16px !important;
|
border-radius: var(--erp-radius-md) !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: #fef0f0 !important;
|
background: #eff6ff !important;
|
||||||
color: #e60023 !important;
|
color: #2563eb !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-menu .ant-menu-item-selected .anticon {
|
.erp-sidebar-menu .ant-menu-item-selected .anticon {
|
||||||
color: #e60023 !important;
|
color: #2563eb !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: #f6f6f3 !important;
|
background: #f1f5f9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar group label */
|
/* Sidebar group label */
|
||||||
@@ -565,17 +565,17 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.8px;
|
letter-spacing: 0.8px;
|
||||||
color: #91918c;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
* MainLayout — CSS classes replacing inline styles
|
* MainLayout — CSS classes replacing inline styles
|
||||||
* ==================================================================== */
|
* ==================================================================== */
|
||||||
|
|
||||||
/* Sider — White sidebar, Notion style */
|
/* Sider — White sidebar, Soft UI style */
|
||||||
.erp-sider-dark {
|
.erp-sider-dark {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
border-right: 1px solid #e0e0d9 !important;
|
border-right: 1px solid #e2e8f0 !important;
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -585,22 +585,22 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sider-dark {
|
[data-theme='dark'] .erp-sider-dark {
|
||||||
background: #211922 !important;
|
background: #0f172a !important;
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.06) !important;
|
border-right: 1px solid #334155 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logo — Warm neutral, Notion style */
|
/* Logo */
|
||||||
.erp-sidebar-logo {
|
.erp-sidebar-logo {
|
||||||
height: 56px;
|
height: 56px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
border-bottom: 1px solid #e0e0d9;
|
border-bottom: 1px solid #e2e8f0;
|
||||||
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.06);
|
border-bottom: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: 16px;
|
border-radius: var(--erp-radius-sm);
|
||||||
background: #e60023;
|
background: #2563eb;
|
||||||
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: #211922;
|
color: #0f172a;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.3px;
|
letter-spacing: -0.3px;
|
||||||
@@ -635,16 +635,16 @@ body {
|
|||||||
color: rgba(255, 255, 255, 0.95);
|
color: rgba(255, 255, 255, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar menu item — White sidebar, warm text */
|
/* Sidebar menu item */
|
||||||
.erp-sidebar-item {
|
.erp-sidebar-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 1px 8px;
|
margin: 1px 8px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border-radius: 16px;
|
border-radius: var(--erp-radius-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #62625b;
|
color: #475569;
|
||||||
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: #f6f6f3;
|
background: #f1f5f9;
|
||||||
color: #211922;
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-item {
|
[data-theme='dark'] .erp-sidebar-item {
|
||||||
color: #91918c;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
[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: #33332e;
|
background: #1e293b;
|
||||||
color: rgba(255, 255, 255, 0.95);
|
color: rgba(255, 255, 255, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-item-active {
|
.erp-sidebar-item-active {
|
||||||
background: #fef0f0;
|
background: #eff6ff;
|
||||||
color: #e60023;
|
color: #2563eb;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-item-active {
|
[data-theme='dark'] .erp-sidebar-item-active {
|
||||||
background: rgba(230, 0, 35, 0.15);
|
background: rgba(37, 99, 235, 0.15);
|
||||||
color: #f05a5a;
|
color: #60a5fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-item-icon {
|
.erp-sidebar-item-icon {
|
||||||
@@ -691,16 +691,16 @@ body {
|
|||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar sub-menu (plugin group) — Warm gray group headers */
|
/* Sidebar sub-menu (plugin group) */
|
||||||
.erp-sidebar-submenu-title {
|
.erp-sidebar-submenu-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin: 6px 8px 2px 8px;
|
margin: 6px 8px 2px 8px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border-radius: 16px;
|
border-radius: var(--erp-radius-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #91918c;
|
color: #94a3b8;
|
||||||
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: #f6f6f3;
|
background: #f1f5f9;
|
||||||
color: #62625b;
|
color: #475569;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-submenu-title {
|
[data-theme='dark'] .erp-sidebar-submenu-title {
|
||||||
color: #62625b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-submenu-title:hover {
|
[data-theme='dark'] .erp-sidebar-submenu-title:hover {
|
||||||
background: #33332e;
|
background: #1e293b;
|
||||||
color: #91918c;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-submenu-title-active {
|
.erp-sidebar-submenu-title-active {
|
||||||
color: #e60023;
|
color: #2563eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-sidebar-submenu-title-active {
|
[data-theme='dark'] .erp-sidebar-submenu-title-active {
|
||||||
color: #f05a5a;
|
color: #60a5fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-sidebar-submenu-arrow {
|
.erp-sidebar-submenu-arrow {
|
||||||
@@ -753,10 +753,10 @@ 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: #f6f6f3; }
|
.erp-main-layout-light { background: #f8fafc; }
|
||||||
.erp-main-layout-dark { background: #1a1a18; }
|
.erp-main-layout-dark { background: #0f172a; }
|
||||||
|
|
||||||
/* Header — Clean white, whisper border bottom */
|
/* Header */
|
||||||
.erp-header {
|
.erp-header {
|
||||||
height: 56px !important;
|
height: 56px !important;
|
||||||
padding: 0 24px !important;
|
padding: 0 24px !important;
|
||||||
@@ -771,43 +771,43 @@ body {
|
|||||||
|
|
||||||
.erp-header-light {
|
.erp-header-light {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
border-bottom: 1px solid #e0e0d9;
|
border-bottom: 1px solid #e2e8f0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-dark {
|
.erp-header-dark {
|
||||||
background: #2a2a28 !important;
|
background: #1e293b !important;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
border-bottom: 1px solid #334155;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-btn {
|
.erp-header-btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 16px;
|
border-radius: var(--erp-radius-md);
|
||||||
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: #62625b;
|
color: #475569;
|
||||||
will-change: background;
|
will-change: background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-light .erp-header-btn { color: #62625b; }
|
.erp-header-light .erp-header-btn { color: #475569; }
|
||||||
.erp-header-dark .erp-header-btn { color: #91918c; }
|
.erp-header-dark .erp-header-btn { color: #94a3b8; }
|
||||||
.erp-header-btn:hover { background: #f6f6f3; }
|
.erp-header-btn:hover { background: #f1f5f9; }
|
||||||
.erp-header-dark .erp-header-btn:hover { background: #33332e; }
|
.erp-header-dark .erp-header-btn:hover { background: #334155; }
|
||||||
|
|
||||||
.erp-header-title { font-size: 15px; font-weight: 600; }
|
.erp-header-title { font-size: 15px; font-weight: 600; }
|
||||||
.erp-text-light { color: #211922; }
|
.erp-text-light { color: #0f172a; }
|
||||||
.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: #62625b; }
|
.erp-text-light-secondary { color: #475569; }
|
||||||
.erp-text-dark-secondary { color: #91918c; }
|
.erp-text-dark-secondary { color: #94a3b8; }
|
||||||
|
|
||||||
.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); }
|
||||||
.erp-header-divider-dark { background: rgba(255, 255, 255, 0.05); }
|
.erp-header-divider-dark { background: rgba(255, 255, 255, 0.06); }
|
||||||
|
|
||||||
/* User avatar */
|
/* User avatar */
|
||||||
.erp-header-user {
|
.erp-header-user {
|
||||||
@@ -816,15 +816,15 @@ body {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 8px;
|
border-radius: var(--erp-radius-sm);
|
||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-header-user:hover { background: #f6f6f3; }
|
.erp-header-user:hover { background: #f1f5f9; }
|
||||||
.erp-header-dark .erp-header-user:hover { background: #33332e; }
|
.erp-header-dark .erp-header-user:hover { background: #334155; }
|
||||||
|
|
||||||
.erp-user-avatar {
|
.erp-user-avatar {
|
||||||
background: #0075de !important;
|
background: #2563eb !important;
|
||||||
font-size: 13px !important;
|
font-size: 13px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,11 +832,11 @@ 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: #91918c; }
|
.erp-footer-light { color: #94a3b8; }
|
||||||
.erp-footer-dark { color: #62625b; }
|
.erp-footer-dark { color: #64748b; }
|
||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
* Dashboard — Stat Cards & Quick Actions (replacing inline styles)
|
* Dashboard — Stat Cards & Quick Actions
|
||||||
* ==================================================================== */
|
* ==================================================================== */
|
||||||
|
|
||||||
/* Stat Card */
|
/* Stat Card */
|
||||||
@@ -864,7 +864,7 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background: var(--card-gradient, linear-gradient(135deg, #e60023, #f05a5a));
|
background: var(--card-gradient, linear-gradient(135deg, #2563eb, #60a5fa));
|
||||||
}
|
}
|
||||||
|
|
||||||
.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(230, 0, 35, 0.08));
|
background: var(--card-icon-bg, rgba(37, 99, 235, 0.08));
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -903,7 +903,7 @@ body {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Section Header (shared by dashboard sections) */
|
/* Section Header */
|
||||||
.erp-section-header {
|
.erp-section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -913,7 +913,7 @@ body {
|
|||||||
|
|
||||||
.erp-section-icon {
|
.erp-section-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #e60023;
|
color: #2563eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: 12px;
|
border-radius: var(--erp-radius-lg);
|
||||||
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: #fef0f0;
|
background: #eff6ff;
|
||||||
border-color: var(--action-color, #e60023);
|
border-color: var(--action-color, #2563eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-quick-action {
|
[data-theme='dark'] .erp-quick-action {
|
||||||
background: #1a1a18;
|
background: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-quick-action:hover {
|
[data-theme='dark'] .erp-quick-action:hover {
|
||||||
background: #33332e;
|
background: #1e293b;
|
||||||
border-color: var(--action-color, #e60023);
|
border-color: var(--action-color, #2563eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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, #e60023) 8%, transparent);
|
background: color-mix(in srgb, var(--action-color, #2563eb) 8%, transparent);
|
||||||
color: var(--action-color, #e60023);
|
color: var(--action-color, #2563eb);
|
||||||
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: #0b5030; }
|
.erp-stat-card-trend-up { color: #059669; }
|
||||||
.erp-stat-card-trend-down { color: #9e0a0a; }
|
.erp-stat-card-trend-down { color: #dc2626; }
|
||||||
.erp-stat-card-trend-neutral { color: #62625b; }
|
.erp-stat-card-trend-neutral { color: #475569; }
|
||||||
|
|
||||||
.erp-stat-card-trend-label {
|
.erp-stat-card-trend-label {
|
||||||
color: #62625b;
|
color: #475569;
|
||||||
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, #e60023) 8%, transparent);
|
background: color-mix(in srgb, var(--action-color, #2563eb) 8%, transparent);
|
||||||
color: var(--action-color, #e60023);
|
color: var(--action-color, #2563eb);
|
||||||
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, #e60023);
|
border-left: 3px solid var(--task-color, #2563eb);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
@@ -1088,12 +1088,12 @@ body {
|
|||||||
.erp-task-item-icon {
|
.erp-task-item-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 8px;
|
border-radius: var(--erp-radius-sm);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: color-mix(in srgb, var(--task-color, #e60023) 8%, transparent);
|
background: color-mix(in srgb, var(--task-color, #2563eb) 8%, transparent);
|
||||||
color: var(--task-color, #e60023);
|
color: var(--task-color, #2563eb);
|
||||||
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: #91918c;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: 12px;
|
border-radius: var(--erp-radius-sm);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erp-task-priority-high { background: #fef2f2; color: #9e0a0a; }
|
.erp-task-priority-high { background: #fef2f2; color: #dc2626; }
|
||||||
.erp-task-priority-medium { background: #fff7ed; color: #b56e1a; }
|
.erp-task-priority-medium { background: #fffbeb; color: #d97706; }
|
||||||
.erp-task-priority-low { background: #ecfdf5; color: #103c25; }
|
.erp-task-priority-low { background: #ecfdf5; color: #059669; }
|
||||||
|
|
||||||
[data-theme='dark'] .erp-task-priority-high { background: rgba(158, 10, 10, 0.15); color: #f05a5a; }
|
[data-theme='dark'] .erp-task-priority-high { background: rgba(220, 38, 38, 0.15); color: #f87171; }
|
||||||
[data-theme='dark'] .erp-task-priority-medium { background: rgba(181, 110, 26, 0.15); color: #d4852a; }
|
[data-theme='dark'] .erp-task-priority-medium { background: rgba(217, 119, 6, 0.15); color: #fbbf24; }
|
||||||
[data-theme='dark'] .erp-task-priority-low { background: rgba(16, 60, 37, 0.15); color: #3db377; }
|
[data-theme='dark'] .erp-task-priority-low { background: rgba(5, 150, 105, 0.15); color: #34d399; }
|
||||||
|
|
||||||
/* 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: #91918c;
|
color: #94a3b8;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] .erp-activity-time {
|
[data-theme='dark'] .erp-activity-time {
|
||||||
color: #62625b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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, #e60023, #f05a5a)',
|
gradient: 'linear-gradient(135deg, #2563eb, #60a5fa)',
|
||||||
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, #103c25, #10B981)',
|
gradient: 'linear-gradient(135deg, #059669, #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, #b56e1a, #F59E0B)',
|
gradient: 'linear-gradient(135deg, #d97706, #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: '#e60023' },
|
{ icon: <UserOutlined />, label: '用户管理', path: '/users', color: '#2563eb' },
|
||||||
{ icon: <SafetyCertificateOutlined />, label: '权限管理', path: '/roles', color: '#103c25' },
|
{ icon: <SafetyCertificateOutlined />, label: '权限管理', path: '/roles', color: '#059669' },
|
||||||
{ icon: <ApartmentOutlined />, label: '组织架构', path: '/organizations', color: '#b56e1a' },
|
{ icon: <ApartmentOutlined />, label: '组织架构', path: '/organizations', color: '#d97706' },
|
||||||
{ 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: '#62625b' },
|
{ icon: <SettingOutlined />, label: '系统设置', path: '/settings', color: '#475569' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const pendingTasks: TaskItem[] = [
|
const pendingTasks: TaskItem[] = [
|
||||||
{ id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#9e0a0a', icon: <UserOutlined />, path: '/users' },
|
{ id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#dc2626', icon: <UserOutlined />, path: '/users' },
|
||||||
{ id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#b56e1a', icon: <PartitionOutlined />, path: '/workflow' },
|
{ id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#d97706', icon: <PartitionOutlined />, path: '/workflow' },
|
||||||
{ id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#103c25', icon: <SafetyCertificateOutlined />, path: '/roles' },
|
{ id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#059669', 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 ? '#f6f6f3' : 'rgba(0,0,0,0.95)',
|
color: isDark ? '#f8fafc' : '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 ? '#91918c' : '#62625b', margin: 0 }}>
|
<p style={{ fontSize: 14, color: isDark ? '#94a3b8' : '#475569', margin: 0 }}>
|
||||||
欢迎回来,这是您的系统概览
|
欢迎回来,这是您的系统概览
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,12 +308,12 @@ export default function Home() {
|
|||||||
<Col xs={24} lg={14}>
|
<Col xs={24} lg={14}>
|
||||||
<div className="erp-content-card erp-fade-in erp-fade-in-delay-2">
|
<div className="erp-content-card erp-fade-in erp-fade-in-delay-2">
|
||||||
<div className="erp-section-header">
|
<div className="erp-section-header">
|
||||||
<CheckCircleOutlined className="erp-section-icon" style={{ color: '#E11D48' }} />
|
<CheckCircleOutlined className="erp-section-icon" style={{ color: '#2563eb' }} />
|
||||||
<span className="erp-section-title">待办任务</span>
|
<span className="erp-section-title">待办任务</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: isDark ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
}}>
|
}}>
|
||||||
{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 ? '#62625b' : '#CBD5E1', fontSize: 12 }} />
|
<RightOutlined style={{ color: isDark ? '#475569' : '#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: '#f05a5a' }} />
|
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#60a5fa' }} />
|
||||||
<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: '#f05a5a' }} />
|
<ClockCircleOutlined className="erp-section-icon" style={{ color: '#60a5fa' }} />
|
||||||
<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%, #e60023 50%, #f05a5a 100%)',
|
background: 'linear-gradient(135deg, #312E81 0%, #2563eb 50%, #60a5fa 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: '#62625b' }}>
|
<p style={{ fontSize: 14, color: '#475569' }}>
|
||||||
请登录您的账户以继续
|
请登录您的账户以继续
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export default function Login() {
|
|||||||
rules={[{ required: true, message: '请输入用户名' }]}
|
rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<UserOutlined style={{ color: '#91918c' }} />}
|
prefix={<UserOutlined style={{ color: '#94a3b8' }} />}
|
||||||
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: '#91918c' }} />}
|
prefix={<LockOutlined style={{ color: '#94a3b8' }} />}
|
||||||
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: '#62625b', margin: 0 }}>
|
<p style={{ fontSize: 12, color: '#475569', 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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 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 ? '#211922' : '#fef0f0',
|
background: isDark ? '#0f172a' : '#eff6ff',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: '#e60023',
|
color: '#2563eb',
|
||||||
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 ? '#211922' : '#ECFDF5',
|
background: isDark ? '#0f172a' : '#ECFDF5',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: '#103c25',
|
color: '#059669',
|
||||||
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: '#e60023' }} />
|
<ApartmentOutlined style={{ marginRight: 8, color: '#2563eb' }} />
|
||||||
组织架构管理
|
组织架构管理
|
||||||
</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 ? '#211922' : '#f6f6f3'}`,
|
borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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 ? '#211922' : '#f6f6f3'}`,
|
borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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 ? '#211922' : '#f6f6f3'}`,
|
borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#62625b', label: '已上传' },
|
uploaded: { color: '#475569', label: '已上传' },
|
||||||
installed: { color: '#2563EB', label: '已安装' },
|
installed: { color: '#2563EB', label: '已安装' },
|
||||||
enabled: { color: '#103c25', label: '已启用' },
|
enabled: { color: '#059669', label: '已启用' },
|
||||||
running: { color: '#103c25', label: '运行中' },
|
running: { color: '#059669', label: '运行中' },
|
||||||
disabled: { color: '#9e0a0a', label: '已禁用' },
|
disabled: { color: '#dc2626', 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: '#62625b', label: status };
|
const cfg = STATUS_CONFIG[status] || { color: '#475569', 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 ? '#f6f6f3' : 'rgba(0,0,0,0.95)',
|
color: isDark ? '#f8fafc' : '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 ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
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: '#e60023' }}
|
style={{ color: '#2563eb' }}
|
||||||
/>
|
/>
|
||||||
<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' ? '#e60023' : '#3B82F6' }}
|
style={{ color: currentPalette.tagColor === 'purple' ? '#2563eb' : '#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 = '#e60023';
|
let nodeColorBase = '#2563eb';
|
||||||
let nodeColorLight = '#f05a5a';
|
let nodeColorLight = '#60a5fa';
|
||||||
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> = {
|
||||||
'财务': '#103c25',
|
'财务': '#059669',
|
||||||
'CRM': '#2563EB',
|
'CRM': '#2563EB',
|
||||||
'进销存': '#9333EA',
|
'进销存': '#9333EA',
|
||||||
'生产': '#9e0a0a',
|
'生产': '#dc2626',
|
||||||
'人力资源': '#b56e1a',
|
'人力资源': '#d97706',
|
||||||
'基础': '#62625b',
|
'基础': '#475569',
|
||||||
};
|
};
|
||||||
|
|
||||||
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 ?? ''] ?? '#62625b'}
|
color={CATEGORY_COLORS[plugin.category ?? ''] ?? '#475569'}
|
||||||
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 ?? ''] ?? '#62625b'}>
|
<Tag color={CATEGORY_COLORS[selectedPlugin.category ?? ''] ?? '#475569'}>
|
||||||
{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, #e60023, #f05a5a)'
|
? 'linear-gradient(135deg, #2563eb, #60a5fa)'
|
||||||
: isDark ? '#211922' : '#f6f6f3',
|
: isDark ? '#0f172a' : '#f8fafc',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
color: record.is_system ? '#fff' : isDark ? '#91918c' : '#62625b',
|
color: record.is_system ? '#fff' : isDark ? '#94a3b8' : '#475569',
|
||||||
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 ? '#211922' : '#f6f6f3',
|
background: isDark ? '#0f172a' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
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 ? '#62625b' : '#91918c' }}>{v || '-'}</span>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8' }}>{v || '-'}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,8 +201,8 @@ export default function Roles() {
|
|||||||
render: (v: boolean) => (
|
render: (v: boolean) => (
|
||||||
<Tag
|
<Tag
|
||||||
style={{
|
style={{
|
||||||
color: v ? '#e60023' : (isDark ? '#91918c' : '#62625b'),
|
color: v ? '#2563eb' : (isDark ? '#94a3b8' : '#475569'),
|
||||||
background: v ? '#fef0f0' : (isDark ? '#211922' : '#f6f6f3'),
|
background: v ? '#eff6ff' : (isDark ? '#0f172a' : '#f8fafc'),
|
||||||
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: '#e60023' }}
|
style={{ color: '#2563eb' }}
|
||||||
>
|
>
|
||||||
权限
|
权限
|
||||||
</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 ? '#91918c' : '#62625b' }}
|
style={{ color: isDark ? '#94a3b8' : '#475569' }}
|
||||||
/>
|
/>
|
||||||
<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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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 ? '#211922' : '#E2E8F0'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#E2E8F0'}`,
|
||||||
background: isDark ? '#0B0F1A' : '#fafaf8',
|
background: isDark ? '#0B0F1A' : '#f1f5f9',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<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: '#103c25',
|
active: '#059669',
|
||||||
disabled: '#9e0a0a',
|
disabled: '#dc2626',
|
||||||
locked: '#b56e1a',
|
locked: '#d97706',
|
||||||
};
|
};
|
||||||
|
|
||||||
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, #e60023, #f05a5a)',
|
background: 'linear-gradient(135deg, #2563eb, #60a5fa)',
|
||||||
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 ? '#62625b' : '#91918c' }}>
|
<div style={{ fontSize: 12, color: isDark ? '#475569' : '#94a3b8' }}>
|
||||||
{record.display_name}
|
{record.display_name}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -262,7 +262,7 @@ export default function Users() {
|
|||||||
<Tag
|
<Tag
|
||||||
style={{
|
style={{
|
||||||
color: STATUS_COLOR_MAP[status] || '#62625b',
|
color: STATUS_COLOR_MAP[status] || '#62625b',
|
||||||
background: STATUS_BG_MAP[status] || '#f6f6f3',
|
background: STATUS_BG_MAP[status] || '#f8fafc',
|
||||||
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 ? '#211922' : '#f6f6f3',
|
background: isDark ? '#0f172a' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#CBD5E1' : '#62625b',
|
color: isDark ? '#CBD5E1' : '#475569',
|
||||||
}}>
|
}}>
|
||||||
{r.name}
|
{r.name}
|
||||||
</Tag>
|
</Tag>
|
||||||
))
|
))
|
||||||
: <span style={{ color: isDark ? '#62625b' : '#CBD5E1' }}>-</span>,
|
: <span style={{ color: isDark ? '#475569' : '#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 ? '#91918c' : '#62625b' }}
|
style={{ color: isDark ? '#94a3b8' : '#475569' }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
icon={<SafetyCertificateOutlined />}
|
icon={<SafetyCertificateOutlined />}
|
||||||
onClick={() => openRoleModal(record)}
|
onClick={() => openRoleModal(record)}
|
||||||
style={{ color: isDark ? '#91918c' : '#62625b' }}
|
style={{ color: isDark ? '#94a3b8' : '#475569' }}
|
||||||
/>
|
/>
|
||||||
{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: '#103c25' }}
|
style={{ color: '#059669' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
@@ -356,7 +356,7 @@ export default function Users() {
|
|||||||
<Space size={8}>
|
<Space size={8}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索用户名..."
|
placeholder="搜索用户名..."
|
||||||
prefix={<SearchOutlined style={{ color: '#91918c' }} />}
|
prefix={<SearchOutlined style={{ color: '#94a3b8' }} />}
|
||||||
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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#91918c' }} />} disabled={!!editUser} />
|
<Input prefix={<UserOutlined style={{ color: '#94a3b8' }} />} 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 ? '#211922' : '#E2E8F0'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#E2E8F0'}`,
|
||||||
background: isDark ? '#0B0F1A' : '#fafaf8',
|
background: isDark ? '#0B0F1A' : '#f1f5f9',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<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 ? '#62625b' : '#91918c', marginLeft: 8, fontSize: 12 }}>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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: '#f05a5a', volcano: '#F97316',
|
lime: '#84CC16', geekblue: '#60a5fa', 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 || '#e60023';
|
const color = widget.color || '#2563eb';
|
||||||
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 ? '#91918c' : '#62625b' };
|
const axisLabelStyle = { fill: isDark ? '#94a3b8' : '#475569' };
|
||||||
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 ? '#91918c' : '#62625b' };
|
const axisLabelStyle = { fill: isDark ? '#94a3b8' : '#475569' };
|
||||||
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 || '#e60023'}10`,
|
background: `${sc.card.color || '#2563eb'}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 || '#e60023'}20`,
|
background: `${sc.card.color || '#2563eb'}20`,
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
color: sc.card.color || '#e60023', fontSize: 18,
|
color: sc.card.color || '#2563eb', fontSize: 18,
|
||||||
}}>
|
}}>
|
||||||
<DashboardOutlined />
|
<DashboardOutlined />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import {
|
|||||||
// ── 通用调色板 ──
|
// ── 通用调色板 ──
|
||||||
|
|
||||||
const UNIVERSAL_COLORS = [
|
const UNIVERSAL_COLORS = [
|
||||||
{ gradient: 'linear-gradient(135deg, #e60023, #f05a5a)', iconBg: 'rgba(79, 70, 229, 0.12)', tagColor: 'purple' },
|
{ gradient: 'linear-gradient(135deg, #2563eb, #60a5fa)', iconBg: 'rgba(79, 70, 229, 0.12)', tagColor: 'purple' },
|
||||||
{ gradient: 'linear-gradient(135deg, #103c25, #10B981)', iconBg: 'rgba(5, 150, 105, 0.12)', tagColor: 'green' },
|
{ gradient: 'linear-gradient(135deg, #059669, #10B981)', iconBg: 'rgba(5, 150, 105, 0.12)', tagColor: 'green' },
|
||||||
{ gradient: 'linear-gradient(135deg, #b56e1a, #F59E0B)', iconBg: 'rgba(217, 119, 6, 0.12)', tagColor: 'orange' },
|
{ gradient: 'linear-gradient(135deg, #d97706, #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: '#e60023', light: '#f05a5a', glow: 'rgba(79,70,229,0.3)' },
|
{ base: '#2563eb', light: '#60a5fa', glow: 'rgba(79,70,229,0.3)' },
|
||||||
{ base: '#103c25', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
{ base: '#059669', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
||||||
{ base: '#b56e1a', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
|
{ base: '#d97706', 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: '#9e0a0a', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
|
{ base: '#dc2626', 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: '#e60023' },
|
in_app: { label: '站内', color: '#2563eb' },
|
||||||
email: { label: '邮件', color: '#103c25' },
|
email: { label: '邮件', color: '#059669' },
|
||||||
sms: { label: '短信', color: '#b56e1a' },
|
sms: { label: '短信', color: '#d97706' },
|
||||||
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 ? '#211922' : '#f6f6f3',
|
background: isDark ? '#0f172a' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
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: '#62625b' };
|
const info = channelMap[c] || { label: c, color: '#475569' };
|
||||||
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 ? '#62625b' : '#91918c', fontSize: 13 }}>{v}</span>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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 ? '#62625b' : '#91918c' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#475569' : '#94a3b8' }}>
|
||||||
共 {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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#9e0a0a', text: '紧急' },
|
urgent: { bg: '#FEF2F2', color: '#dc2626', text: '紧急' },
|
||||||
important: { bg: '#FFFBEB', color: '#b56e1a', text: '重要' },
|
important: { bg: '#FFFBEB', color: '#d97706', text: '重要' },
|
||||||
normal: { bg: '#fef0f0', color: '#e60023', text: '普通' },
|
normal: { bg: '#eff6ff', color: '#2563eb', 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 ? '#62625b' : '#91918c', fontSize: 12 }}>
|
<div style={{ marginTop: 8, color: isDark ? '#475569' : '#94a3b8', 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 ? '#91918c' : '#62625b') : 'inherit',
|
color: record.is_read ? (isDark ? '#94a3b8' : '#475569') : '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: '#e60023',
|
background: '#2563eb',
|
||||||
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: '#f6f6f3', color: '#62625b', text: p };
|
const info = priorityStyles[p] || { bg: '#f8fafc', color: '#475569', 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 ? '#62625b' : '#91918c' }}>{s === 'system' ? '系统' : '用户'}</span>,
|
render: (s: string) => <span style={{ color: isDark ? '#475569' : '#94a3b8' }}>{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 ? '#211922' : '#f6f6f3') : '#fef0f0',
|
background: r ? (isDark ? '#0f172a' : '#f8fafc') : '#eff6ff',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: r ? (isDark ? '#62625b' : '#91918c') : '#e60023',
|
color: r ? (isDark ? '#475569' : '#94a3b8') : '#2563eb',
|
||||||
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 ? '#62625b' : '#91918c', fontSize: 13 }}>{v}</span>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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: '#e60023' }}
|
style={{ color: '#2563eb' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<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 ? '#62625b' : '#91918c' }}
|
style={{ color: isDark ? '#475569' : '#94a3b8' }}
|
||||||
/>
|
/>
|
||||||
<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 ? '#62625b' : '#91918c' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#475569' : '#94a3b8' }}>
|
||||||
共 {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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#e60023' }} />
|
<BellOutlined style={{ fontSize: 16, color: '#2563eb' }} />
|
||||||
<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: '#e60023', light: '#f05a5a', glow: 'rgba(79,70,229,0.3)' },
|
{ base: '#2563eb', light: '#60a5fa', glow: 'rgba(79,70,229,0.3)' },
|
||||||
{ base: '#103c25', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
{ base: '#059669', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
|
||||||
{ base: '#b56e1a', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
|
{ base: '#d97706', 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: '#9e0a0a', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
|
{ base: '#dc2626', 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 = '#e60023';
|
let nodeColorBase = '#2563eb';
|
||||||
let nodeColorLight = '#f05a5a';
|
let nodeColorLight = '#60a5fa';
|
||||||
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: '#103c25', text: '创建' },
|
create: { bg: '#ECFDF5', color: '#059669', text: '创建' },
|
||||||
update: { bg: '#fef0f0', color: '#e60023', text: '更新' },
|
update: { bg: '#eff6ff', color: '#2563eb', text: '更新' },
|
||||||
delete: { bg: '#FEF2F2', color: '#9e0a0a', text: '删除' },
|
delete: { bg: '#FEF2F2', color: '#dc2626', 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: '#f6f6f3', color: '#62625b', text: action };
|
const info = ACTION_STYLES[action] || { bg: '#f8fafc', color: '#475569', 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 ? '#211922' : '#f6f6f3',
|
background: isDark ? '#0f172a' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#CBD5E1' : '#62625b',
|
color: isDark ? '#CBD5E1' : '#475569',
|
||||||
}}>
|
}}>
|
||||||
{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 ? '#91918c' : '#62625b' }}>
|
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#94a3b8' : '#475569' }}>
|
||||||
{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 ? '#91918c' : '#62625b' }}>
|
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#94a3b8' : '#475569' }}>
|
||||||
{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 ? '#62625b' : '#91918c', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
}}>
|
}}>
|
||||||
<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 ? '#62625b' : '#91918c', marginLeft: 'auto' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#475569' : '#94a3b8', 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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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 ? '#211922' : '#f6f6f3',
|
background: isDark ? '#0f172a' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#CBD5E1' : '#62625b',
|
color: isDark ? '#CBD5E1' : '#475569',
|
||||||
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 ? '#91918c' : '#62625b' }}
|
style={{ color: isDark ? '#94a3b8' : '#475569' }}
|
||||||
/>
|
/>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定删除此设置?"
|
title="确定删除此设置?"
|
||||||
@@ -191,7 +191,7 @@ export default function SystemSettings() {
|
|||||||
<Space>
|
<Space>
|
||||||
<Input
|
<Input
|
||||||
placeholder="输入设置键名查询"
|
placeholder="输入设置键名查询"
|
||||||
prefix={<SearchOutlined style={{ color: '#91918c' }} />}
|
prefix={<SearchOutlined style={{ color: '#94a3b8' }} />}
|
||||||
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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#103c25', text: '同意' },
|
approved: { bg: '#ECFDF5', color: '#059669', text: '同意' },
|
||||||
rejected: { bg: '#FEF2F2', color: '#9e0a0a', text: '拒绝' },
|
rejected: { bg: '#FEF2F2', color: '#dc2626', text: '拒绝' },
|
||||||
delegated: { bg: '#fef0f0', color: '#e60023', text: '已委派' },
|
delegated: { bg: '#eff6ff', color: '#2563eb', 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: '#f6f6f3', color: '#62625b', text: o };
|
const info = outcomeStyles[o] || { bg: '#f8fafc', color: '#475569', 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 ? '#62625b' : '#91918c', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#fef0f0', color: '#e60023', text: '运行中' },
|
running: { bg: '#eff6ff', color: '#2563eb', text: '运行中' },
|
||||||
suspended: { bg: '#FFFBEB', color: '#b56e1a', text: '已挂起' },
|
suspended: { bg: '#FFFBEB', color: '#d97706', text: '已挂起' },
|
||||||
completed: { bg: '#ECFDF5', color: '#103c25', text: '已完成' },
|
completed: { bg: '#ECFDF5', color: '#059669', text: '已完成' },
|
||||||
terminated: { bg: '#FEF2F2', color: '#9e0a0a', text: '已终止' },
|
terminated: { bg: '#FEF2F2', color: '#dc2626', 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: '#f6f6f3', color: '#62625b', text: s };
|
const info = statusStyles[s] || { bg: '#f8fafc', color: '#475569', 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 ? '#62625b' : '#91918c', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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 ? '#211922' : '#f6f6f3',
|
background: isDark ? '#0f172a' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
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: '#fef0f0',
|
background: '#eff6ff',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: '#e60023',
|
color: '#2563eb',
|
||||||
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 ? '#62625b' : '#91918c', fontSize: 13 }}>
|
<span style={{ color: isDark ? '#475569' : '#94a3b8', 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 ? '#211922' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||||
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: '#f6f6f3', color: '#62625b', text: '草稿' },
|
draft: { bg: '#f8fafc', color: '#475569', text: '草稿' },
|
||||||
published: { bg: '#ecfdf5', color: '#103c25', text: '已发布' },
|
published: { bg: '#ecfdf5', color: '#059669', text: '已发布' },
|
||||||
deprecated: { bg: '#fef2f2', color: '#9e0a0a', text: '已弃用' },
|
deprecated: { bg: '#fef2f2', color: '#dc2626', 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' : '#f6f6f3',
|
background: isDark ? '#1E293B' : '#f8fafc',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: isDark ? '#91918c' : '#62625b',
|
color: isDark ? '#94a3b8' : '#475569',
|
||||||
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: '#f6f6f3', color: '#62625b', text: s };
|
const info = statusColors[s] || { bg: '#f8fafc', color: '#475569', 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 ? '#62625b' : '#91918c' }}>
|
<span style={{ fontSize: 13, color: isDark ? '#475569' : '#94a3b8' }}>
|
||||||
共 {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' : '#f6f6f3'}`,
|
border: `1px solid ${isDark ? '#1E293B' : '#f8fafc'}`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
Reference in New Issue
Block a user