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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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