mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
Compare commits
2 Commits
@@ -13,15 +13,15 @@ import { Button } from '@/components/ui/Button';
|
||||
import { PageTransition } from '@/components/common/PageTransition';
|
||||
import { MainRoutes } from '@/router/MainRoutes';
|
||||
import {
|
||||
IconBot,
|
||||
IconChartLine,
|
||||
IconFileText,
|
||||
IconInfo,
|
||||
IconLayoutDashboard,
|
||||
IconScrollText,
|
||||
IconSettings,
|
||||
IconShield,
|
||||
IconTimer,
|
||||
IconSidebarAuthFiles,
|
||||
IconSidebarConfig,
|
||||
IconSidebarDashboard,
|
||||
IconSidebarLogs,
|
||||
IconSidebarOauth,
|
||||
IconSidebarProviders,
|
||||
IconSidebarQuota,
|
||||
IconSidebarSystem,
|
||||
IconSidebarUsage,
|
||||
} from '@/components/ui/icons';
|
||||
import { INLINE_LOGO_JPEG } from '@/assets/logoInline';
|
||||
import {
|
||||
@@ -38,15 +38,15 @@ import { isSupportedLanguage } from '@/utils/language';
|
||||
import type { Theme } from '@/types';
|
||||
|
||||
const sidebarIcons: Record<string, ReactNode> = {
|
||||
dashboard: <IconLayoutDashboard size={18} />,
|
||||
aiProviders: <IconBot size={18} />,
|
||||
authFiles: <IconFileText size={18} />,
|
||||
oauth: <IconShield size={18} />,
|
||||
quota: <IconTimer size={18} />,
|
||||
usage: <IconChartLine size={18} />,
|
||||
config: <IconSettings size={18} />,
|
||||
logs: <IconScrollText size={18} />,
|
||||
system: <IconInfo size={18} />,
|
||||
dashboard: <IconSidebarDashboard size={18} />,
|
||||
aiProviders: <IconSidebarProviders size={18} />,
|
||||
authFiles: <IconSidebarAuthFiles size={18} />,
|
||||
oauth: <IconSidebarOauth size={18} />,
|
||||
quota: <IconSidebarQuota size={18} />,
|
||||
usage: <IconSidebarUsage size={18} />,
|
||||
config: <IconSidebarConfig size={18} />,
|
||||
logs: <IconSidebarLogs size={18} />,
|
||||
system: <IconSidebarSystem size={18} />,
|
||||
};
|
||||
|
||||
// Header action icons - smaller size for header buttons
|
||||
@@ -132,7 +132,13 @@ const headerIcons = {
|
||||
</clipPath>
|
||||
</defs>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<circle cx="12" cy="12" r="4" clipPath="url(#mainLayoutAutoThemeSunLeftHalf)" fill="currentColor" />
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="4"
|
||||
clipPath="url(#mainLayoutAutoThemeSunLeftHalf)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M12 2v2" />
|
||||
<path d="M12 20v2" />
|
||||
<path d="M4.93 4.93l1.41 1.41" />
|
||||
@@ -171,17 +177,35 @@ const THEME_CARDS: Array<{
|
||||
{
|
||||
key: 'white',
|
||||
labelKey: 'theme.white',
|
||||
colors: { bg: '#ffffff', card: '#ffffff', border: '#e5e5e5', text: '#2d2a26', textMuted: '#a29c95' },
|
||||
colors: {
|
||||
bg: '#ffffff',
|
||||
card: '#ffffff',
|
||||
border: '#e5e5e5',
|
||||
text: '#2d2a26',
|
||||
textMuted: '#a29c95',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'light',
|
||||
labelKey: 'theme.light',
|
||||
colors: { bg: '#faf9f5', card: '#f0eee8', border: '#e3e1db', text: '#2d2a26', textMuted: '#a29c95' },
|
||||
colors: {
|
||||
bg: '#faf9f5',
|
||||
card: '#f0eee8',
|
||||
border: '#e3e1db',
|
||||
text: '#2d2a26',
|
||||
textMuted: '#a29c95',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'dark',
|
||||
labelKey: 'theme.dark',
|
||||
colors: { bg: '#151412', card: '#1d1b18', border: '#3a3530', text: '#f6f4f1', textMuted: '#9c958d' },
|
||||
colors: {
|
||||
bg: '#151412',
|
||||
card: '#1d1b18',
|
||||
border: '#3a3530',
|
||||
text: '#f6f4f1',
|
||||
textMuted: '#9c958d',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -420,7 +444,6 @@ export function MainLayout() {
|
||||
});
|
||||
}, [fetchConfig]);
|
||||
|
||||
|
||||
const statusClass =
|
||||
connectionStatus === 'connected'
|
||||
? 'success'
|
||||
@@ -503,7 +526,7 @@ export function MainLayout() {
|
||||
clearCache();
|
||||
const results = await Promise.allSettled([
|
||||
fetchConfig(undefined, true),
|
||||
triggerHeaderRefresh()
|
||||
triggerHeaderRefresh(),
|
||||
]);
|
||||
const rejected = results.find((result) => result.status === 'rejected');
|
||||
if (rejected && rejected.status === 'rejected') {
|
||||
@@ -618,7 +641,10 @@ export function MainLayout() {
|
||||
>
|
||||
{headerIcons.update}
|
||||
</Button>
|
||||
<div className={`language-menu ${languageMenuOpen ? 'open' : ''}`} ref={languageMenuRef}>
|
||||
<div
|
||||
className={`language-menu ${languageMenuOpen ? 'open' : ''}`}
|
||||
ref={languageMenuRef}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -631,7 +657,11 @@ export function MainLayout() {
|
||||
{headerIcons.language}
|
||||
</Button>
|
||||
{languageMenuOpen && (
|
||||
<div className="notification entering language-menu-popover" role="menu" aria-label={t('language.switch')}>
|
||||
<div
|
||||
className="notification entering language-menu-popover"
|
||||
role="menu"
|
||||
aria-label={t('language.switch')}
|
||||
>
|
||||
{LANGUAGE_ORDER.map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
@@ -667,7 +697,11 @@ export function MainLayout() {
|
||||
: headerIcons.sun}
|
||||
</Button>
|
||||
{themeMenuOpen && (
|
||||
<div className="notification entering theme-menu-popover" role="menu" aria-label={t('theme.switch')}>
|
||||
<div
|
||||
className="notification entering theme-menu-popover"
|
||||
role="menu"
|
||||
aria-label={t('theme.switch')}
|
||||
>
|
||||
{THEME_CARDS.map((tc) => (
|
||||
<button
|
||||
key={tc.key}
|
||||
@@ -679,7 +713,10 @@ export function MainLayout() {
|
||||
>
|
||||
<div
|
||||
className="theme-card-preview"
|
||||
style={{ background: tc.colors.bg, border: `1px solid ${tc.colors.border}` }}
|
||||
style={{
|
||||
background: tc.colors.bg,
|
||||
border: `1px solid ${tc.colors.border}`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="theme-card-header"
|
||||
|
||||
+118
-1
@@ -16,7 +16,15 @@ const baseSvgProps: SVGProps<SVGSVGElement> = {
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
'aria-hidden': 'true',
|
||||
focusable: 'false'
|
||||
focusable: 'false',
|
||||
};
|
||||
|
||||
const sidebarSvgProps: SVGProps<SVGSVGElement> = {
|
||||
...baseSvgProps,
|
||||
strokeWidth: 1.72,
|
||||
strokeLinecap: 'square',
|
||||
strokeLinejoin: 'miter',
|
||||
strokeMiterlimit: 10,
|
||||
};
|
||||
|
||||
export function IconSlidersHorizontal({ size = 20, ...props }: IconProps) {
|
||||
@@ -322,3 +330,112 @@ export function IconLayoutDashboard({ size = 20, ...props }: IconProps) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarDashboard({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<rect x="3" y="3" width="7.5" height="8" rx="1.5" />
|
||||
<rect x="13.5" y="3" width="7.5" height="5" rx="1.5" fill="currentColor" fillOpacity="0.12" />
|
||||
<rect x="3" y="14" width="7.5" height="7" rx="1.5" fill="currentColor" fillOpacity="0.12" />
|
||||
<rect x="13.5" y="11" width="7.5" height="10" rx="1.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarConfig({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<path d="M4 8h16" />
|
||||
<path d="M4 16h16" />
|
||||
<circle cx="9.5" cy="8" r="2.8" fill="currentColor" fillOpacity="0.12" />
|
||||
<circle cx="15" cy="16" r="2.8" fill="currentColor" fillOpacity="0.12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarProviders({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<circle cx="12" cy="5.5" r="2.8" fill="currentColor" fillOpacity="0.12" />
|
||||
<circle cx="5.5" cy="18.5" r="2.8" />
|
||||
<circle cx="18.5" cy="18.5" r="2.8" />
|
||||
<path d="M10.2 7.8 7 16.2" />
|
||||
<path d="M13.8 7.8 17 16.2" />
|
||||
<path d="M8.3 18.5h7.4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarAuthFiles({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<path d="M7 3h7l4 4v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z" />
|
||||
<path d="M14 3v4h4" fill="currentColor" fillOpacity="0.12" />
|
||||
<path d="M9 13l2 2 4-4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarOauth({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<path
|
||||
d="M12 3l8 4v5c0 5.25-3.4 8.25-8 10-4.6-1.75-8-4.75-8-10V7Z"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.08"
|
||||
/>
|
||||
<circle cx="12" cy="11" r="1.5" fill="currentColor" stroke="none" />
|
||||
<path d="M12 12.5v2.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarQuota({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<circle cx="12" cy="12" r="8" />
|
||||
<path d="M12 12V4a8 8 0 0 1 8 8Z" fill="currentColor" fillOpacity="0.12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarUsage({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<path d="M3.5 20h17" />
|
||||
<rect x="5" y="13" width="3.5" height="7" rx="0.5" />
|
||||
<rect x="10.25" y="7" width="3.5" height="13" rx="0.5" fill="currentColor" fillOpacity="0.12" />
|
||||
<rect x="15.5" y="10" width="3.5" height="10" rx="0.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarLogs({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||
<path d="M3 8.5h18" />
|
||||
<circle cx="5.5" cy="6.2" r="0.8" fill="currentColor" stroke="none" />
|
||||
<circle cx="7.8" cy="6.2" r="0.8" fill="currentColor" fillOpacity="0.4" stroke="none" />
|
||||
<path d="M7 12l3 2.5-3 2.5" />
|
||||
<path d="M13 17h4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSidebarSystem({ size = 20, ...props }: IconProps) {
|
||||
return (
|
||||
<svg {...sidebarSvgProps} width={size} height={size} {...props}>
|
||||
<rect x="6" y="6" width="12" height="12" rx="2" />
|
||||
<rect x="9" y="9" width="6" height="6" rx="1" fill="currentColor" fillOpacity="0.12" />
|
||||
<path d="M6 10H3" />
|
||||
<path d="M6 14H3" />
|
||||
<path d="M21 10h-3" />
|
||||
<path d="M21 14h-3" />
|
||||
<path d="M10 6V3" />
|
||||
<path d="M14 6V3" />
|
||||
<path d="M10 21v-3" />
|
||||
<path d="M14 21v-3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
+46
-10
@@ -81,7 +81,9 @@
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: background $transition-fast, color $transition-fast;
|
||||
transition:
|
||||
background $transition-fast,
|
||||
color $transition-fast;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -118,7 +120,10 @@
|
||||
max-width: 320px;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: max-width 0.4s ease, opacity 0.4s ease, transform 0.4s ease;
|
||||
transition:
|
||||
max-width 0.4s ease,
|
||||
opacity 0.4s ease,
|
||||
transform 0.4s ease;
|
||||
}
|
||||
|
||||
.brand-abbr {
|
||||
@@ -126,7 +131,9 @@
|
||||
transform: translateX(12px);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||
transition:
|
||||
opacity 0.4s ease,
|
||||
transform 0.4s ease;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
@@ -220,7 +227,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: background-color $transition-fast, color $transition-fast;
|
||||
transition:
|
||||
background-color $transition-fast,
|
||||
color $transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-secondary);
|
||||
@@ -278,7 +287,9 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: border-color $transition-fast, background-color $transition-fast;
|
||||
transition:
|
||||
border-color $transition-fast,
|
||||
background-color $transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-secondary);
|
||||
@@ -439,7 +450,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-lg;
|
||||
transition: width $transition-normal, transform $transition-normal;
|
||||
transition:
|
||||
width $transition-normal,
|
||||
transform $transition-normal;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
@@ -464,22 +477,33 @@
|
||||
.nav-item {
|
||||
padding: 10px 12px;
|
||||
border-radius: $radius-md;
|
||||
border: 1px solid transparent;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
cursor: pointer;
|
||||
transition: background $transition-fast, color $transition-fast;
|
||||
transition:
|
||||
background $transition-fast,
|
||||
color $transition-fast;
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.9;
|
||||
opacity: 0.96;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(180deg, rgb(255 255 255 / 0.18), rgb(255 255 255 / 0))
|
||||
var(--bg-secondary);
|
||||
box-shadow: inset 0 0 0 1px var(--border-primary);
|
||||
transition:
|
||||
background $transition-fast,
|
||||
box-shadow $transition-fast,
|
||||
color $transition-fast;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
@@ -496,12 +520,24 @@
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-secondary);
|
||||
|
||||
.nav-icon {
|
||||
background: linear-gradient(180deg, rgb(255 255 255 / 0.24), rgb(255 255 255 / 0))
|
||||
var(--bg-primary);
|
||||
box-shadow: inset 0 0 0 1px var(--border-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba($primary-color, 0.14);
|
||||
color: var(--primary-color);
|
||||
border: 1px solid rgba($primary-color, 0.35);
|
||||
|
||||
.nav-icon {
|
||||
background: linear-gradient(180deg, rgb(255 255 255 / 0.22), rgb(255 255 255 / 0))
|
||||
rgba($primary-color, 0.1);
|
||||
box-shadow: inset 0 0 0 1px rgba($primary-color, 0.26);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user