1315 lines
43 KiB
JavaScript
1315 lines
43 KiB
JavaScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
|
import {
|
|
AppstoreOutlined,
|
|
CloudOutlined,
|
|
DashboardOutlined,
|
|
DesktopOutlined,
|
|
DownOutlined,
|
|
ExperimentOutlined,
|
|
FundProjectionScreenOutlined,
|
|
LineChartOutlined,
|
|
LogoutOutlined,
|
|
MenuFoldOutlined,
|
|
MenuUnfoldOutlined,
|
|
MessageOutlined,
|
|
MoonOutlined,
|
|
RightOutlined,
|
|
RobotOutlined,
|
|
SettingOutlined,
|
|
ShoppingOutlined,
|
|
TableOutlined,
|
|
TeamOutlined,
|
|
UserOutlined,
|
|
SunOutlined,
|
|
} from '@ant-design/icons';
|
|
import { Dropdown, Switch } from 'antd';
|
|
import { verifyToken } from '../api/auth';
|
|
import { getSetupStatus, getSystemConfig } from '../api/admin';
|
|
import { getInstallStatus } from '../api/install';
|
|
import ErrorBoundary from '../components/ErrorBoundary';
|
|
import { AppShellSkeleton } from '../components/AppLoadingSkeleton';
|
|
import { DEFAULT_SIDEBAR_CONFIG, parseSidebarConfig, sortSidebarEntries } from '../config/sidebarNavigation';
|
|
import {
|
|
canRestoreDesktopSessionImmediately,
|
|
isDesktopEmbedSession,
|
|
markDesktopEmbedSession,
|
|
requestDesktopSessionRenewal,
|
|
} from '../utils/desktopEmbedSession';
|
|
|
|
const AUTH_USER_STORAGE_KEY = 'auth_user';
|
|
const SIDEBAR_DARK_MODE_STORAGE_KEY = 'maixiang_sidebar_dark_mode';
|
|
const BRAND_HOME_URL = 'https://yyd.jiansuntuzhi.cn/analysis/summary';
|
|
const PLANNING_HOME_URL = '/planning/dev-style-preview';
|
|
const FALLBACK_SITE_NAME = '拼多多自动化运营平台';
|
|
const FALLBACK_LOGO = 'https://s3.jiansun.vip/gongxiangpan/logos/1775097467497-1d6a81b8a76a.png';
|
|
const readSidebarDarkModePreference = () => {
|
|
try {
|
|
const value = localStorage.getItem(SIDEBAR_DARK_MODE_STORAGE_KEY);
|
|
if (value === 'dark') return true;
|
|
if (value === 'light') return false;
|
|
} catch {
|
|
// Local persistence is optional in restricted browser contexts.
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const SIDEBAR_ICON_MAP = {
|
|
appstore: AppstoreOutlined,
|
|
cloud: CloudOutlined,
|
|
dashboard: DashboardOutlined,
|
|
desktop: DesktopOutlined,
|
|
experiment: ExperimentOutlined,
|
|
'line-chart': LineChartOutlined,
|
|
message: MessageOutlined,
|
|
projection: FundProjectionScreenOutlined,
|
|
robot: RobotOutlined,
|
|
setting: SettingOutlined,
|
|
shopping: ShoppingOutlined,
|
|
table: TableOutlined,
|
|
team: TeamOutlined,
|
|
};
|
|
|
|
const SIDEBAR_ROUTE_MAP = {
|
|
'dashboard-preview': { path: '/planning/dev-style-preview' },
|
|
'marketing-events': {
|
|
path: '/planning/operation/marketing-events',
|
|
activePath: '/planning/operation/marketing-events',
|
|
matchMode: 'prefix',
|
|
},
|
|
'promotion-module': { path: '/planning/operation/promotion-module' },
|
|
'product-monitoring': { path: '/planning/operation/product-monitoring' },
|
|
'ops-b-incentive': { path: '/planning/operation/ops-b-incentive' },
|
|
'product-analysis': { path: '/planning/operation/product-analysis' },
|
|
'work-orders': { path: '/planning/operation/work-orders' },
|
|
settings: { path: '/settings' },
|
|
'online-desktop-users': { path: '/settings/online-desktop-users' },
|
|
'desktop-execution-tasks': { path: '/settings/desktop-execution-tasks' },
|
|
};
|
|
|
|
const buildSidebarSections = (config, canUseSettings) => sortSidebarEntries(config.entries).flatMap((entry) => {
|
|
if (entry.requiresAdmin && !canUseSettings) return [];
|
|
if (entry.type === 'section') return [{ ...entry }];
|
|
|
|
const route = SIDEBAR_ROUTE_MAP[entry.routeKey] || {};
|
|
const routeMatches = (itemRoute = {}, pathname = '') => {
|
|
const excludedPaths = Array.isArray(itemRoute.excludePaths) ? itemRoute.excludePaths : [];
|
|
if (excludedPaths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {
|
|
return false;
|
|
}
|
|
|
|
const exactPaths = Array.isArray(itemRoute.matchPaths) ? itemRoute.matchPaths : [];
|
|
if (exactPaths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {
|
|
return true;
|
|
}
|
|
|
|
const matchPath = itemRoute.activePath || itemRoute.path;
|
|
if (!matchPath) return false;
|
|
return itemRoute.matchMode === 'prefix'
|
|
? pathname === matchPath || pathname.startsWith(`${matchPath}/`)
|
|
: pathname === matchPath;
|
|
};
|
|
const buildItem = (item) => {
|
|
if (item.requiresAdmin && !canUseSettings) return null;
|
|
const itemRoute = SIDEBAR_ROUTE_MAP[item.routeKey] || {};
|
|
return {
|
|
...item,
|
|
icon: SIDEBAR_ICON_MAP[item.icon] || AppstoreOutlined,
|
|
path: itemRoute.path,
|
|
external: Boolean(itemRoute.externalUrl),
|
|
onClick: itemRoute.externalUrl ? () => { window.location.href = itemRoute.externalUrl; } : undefined,
|
|
match: (path) => routeMatches(itemRoute, path),
|
|
};
|
|
};
|
|
|
|
const normalized = {
|
|
...entry,
|
|
icon: SIDEBAR_ICON_MAP[entry.icon] || AppstoreOutlined,
|
|
path: route.path,
|
|
external: Boolean(route.externalUrl),
|
|
onClick: route.externalUrl ? () => { window.location.href = route.externalUrl; } : undefined,
|
|
match: (path) => routeMatches(route, path),
|
|
};
|
|
|
|
if (entry.type === 'group') {
|
|
normalized.children = sortSidebarEntries(entry.children).map(buildItem).filter(Boolean);
|
|
}
|
|
|
|
return [normalized];
|
|
});
|
|
|
|
function parseJwtPayload(token) {
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const [, payload] = token.split('.');
|
|
if (!payload) {
|
|
return null;
|
|
}
|
|
|
|
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
const padded = normalized.padEnd(normalized.length + ((4 - normalized.length % 4) % 4), '=');
|
|
return JSON.parse(window.atob(padded));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isExpiredJwtPayload(payload) {
|
|
const exp = Number(payload?.exp || 0);
|
|
return exp > 0 && Math.floor(Date.now() / 1000) >= exp;
|
|
}
|
|
|
|
function readStoredAuthUser(tokenSub) {
|
|
try {
|
|
const raw = localStorage.getItem(AUTH_USER_STORAGE_KEY);
|
|
if (!raw) {
|
|
return null;
|
|
}
|
|
|
|
const storedUser = JSON.parse(raw);
|
|
const storedSub = storedUser?.sub ?? storedUser?.id;
|
|
return storedSub && String(storedSub) === String(tokenSub) ? storedUser : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function clearAuthSession() {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem(AUTH_USER_STORAGE_KEY);
|
|
}
|
|
|
|
function looksLikeMojibake(text) {
|
|
return /[ÃÂ][\u0080-\u00ff]|[äåæçèé][\u0080-\u00ff]|[\u00c0-\u00ff][\u0080-\u00bf]/.test(text);
|
|
}
|
|
|
|
function repairMojibake(text) {
|
|
if (!text || !looksLikeMojibake(text) || typeof TextDecoder === 'undefined') {
|
|
return text;
|
|
}
|
|
|
|
try {
|
|
const bytes = Uint8Array.from([...text].map((char) => char.charCodeAt(0) & 0xff));
|
|
const repaired = new TextDecoder('utf-8', { fatal: true }).decode(bytes).trim();
|
|
return repaired || text;
|
|
} catch {
|
|
return text;
|
|
}
|
|
}
|
|
|
|
function normalizeDisplayName(...candidates) {
|
|
const rawName = candidates.find((item) => typeof item === 'string' && item.trim());
|
|
return repairMojibake(rawName?.trim() || '个人中心');
|
|
}
|
|
|
|
function buildFallbackUserFromToken(token) {
|
|
const payload = parseJwtPayload(token);
|
|
if (!payload?.sub || isExpiredJwtPayload(payload)) {
|
|
return null;
|
|
}
|
|
|
|
const storedUser = readStoredAuthUser(payload.sub);
|
|
return {
|
|
sub: payload.sub,
|
|
username: payload.username || '',
|
|
name: payload.name || payload.username || '当前用户',
|
|
email: payload.email || '',
|
|
role: payload.role || storedUser?.role || 'user',
|
|
picture: payload.picture || storedUser?.picture || '',
|
|
authSource: payload.authSource || storedUser?.authSource || 'local',
|
|
departmentId: payload.departmentId ?? storedUser?.departmentId ?? null,
|
|
departmentName: payload.departmentName || storedUser?.departmentName || '',
|
|
allowedPageKeys: Array.isArray(payload.allowedPageKeys)
|
|
? payload.allowedPageKeys
|
|
: Array.isArray(storedUser?.allowedPageKeys)
|
|
? storedUser.allowedPageKeys
|
|
: [],
|
|
};
|
|
}
|
|
|
|
function canFallbackToJwtPayload(error, fallbackUser) {
|
|
if (!fallbackUser) {
|
|
return false;
|
|
}
|
|
|
|
if (fallbackUser.authSource === 'local-preview') {
|
|
return true;
|
|
}
|
|
|
|
const status = Number(error?.response?.status || 0);
|
|
return !status || status >= 500;
|
|
}
|
|
|
|
const BasicLayout = () => {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const [user, setUser] = useState(null);
|
|
const [authReady, setAuthReady] = useState(false);
|
|
const [siteConfig, setSiteConfig] = useState({ siteName: FALLBACK_SITE_NAME, logo: FALLBACK_LOGO });
|
|
const [sidebarConfig, setSidebarConfig] = useState(DEFAULT_SIDEBAR_CONFIG);
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(
|
|
() => DEFAULT_SIDEBAR_CONFIG.collapsedByDefault
|
|
);
|
|
const [sidebarDarkMode, setSidebarDarkMode] = useState(
|
|
() => readSidebarDarkModePreference() ?? DEFAULT_SIDEBAR_CONFIG.darkModeByDefault
|
|
);
|
|
const [expandedSidebarKeys, setExpandedSidebarKeys] = useState(
|
|
DEFAULT_SIDEBAR_CONFIG.entries.filter((entry) => entry.defaultExpanded).map((entry) => entry.key)
|
|
);
|
|
|
|
const pathname = location.pathname;
|
|
const isImmersivePlanningRoute = false;
|
|
const canUseSettings = user?.role === 'admin';
|
|
const userDisplayName = normalizeDisplayName(user?.name, user?.username, user?.email);
|
|
const userAvatar = user?.picture || siteConfig.logo || FALLBACK_LOGO;
|
|
|
|
const sidebarSections = useMemo(
|
|
() => buildSidebarSections(sidebarConfig, canUseSettings),
|
|
[canUseSettings, sidebarConfig]
|
|
);
|
|
useEffect(() => {
|
|
const loadSiteConfig = async () => {
|
|
try {
|
|
const config = await getSystemConfig();
|
|
const nextSiteName = config?.site_name || FALLBACK_SITE_NAME;
|
|
const nextLogo = config?.logo || FALLBACK_LOGO;
|
|
const nextSidebarConfig = parseSidebarConfig(config?.sidebar_navigation);
|
|
|
|
setSiteConfig({ siteName: nextSiteName, logo: nextLogo });
|
|
setSidebarConfig(nextSidebarConfig);
|
|
setSidebarCollapsed(Boolean(nextSidebarConfig.collapsedByDefault));
|
|
setSidebarDarkMode(readSidebarDarkModePreference() ?? Boolean(nextSidebarConfig.darkModeByDefault));
|
|
setExpandedSidebarKeys(
|
|
nextSidebarConfig.entries.filter((entry) => entry.defaultExpanded).map((entry) => entry.key)
|
|
);
|
|
document.title = nextSiteName;
|
|
|
|
let link = document.querySelector("link[rel*='icon']");
|
|
if (!link) {
|
|
link = document.createElement('link');
|
|
link.rel = 'icon';
|
|
document.head.appendChild(link);
|
|
}
|
|
link.href = nextLogo;
|
|
} catch {
|
|
document.title = FALLBACK_SITE_NAME;
|
|
}
|
|
};
|
|
|
|
loadSiteConfig();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const handleSiteConfigUpdated = (event) => {
|
|
const nextSiteName = event.detail?.siteName || FALLBACK_SITE_NAME;
|
|
const nextLogo = event.detail?.logo;
|
|
|
|
setSiteConfig((current) => ({
|
|
siteName: nextSiteName,
|
|
logo: nextLogo || current.logo,
|
|
}));
|
|
document.title = nextSiteName;
|
|
|
|
if (nextLogo) {
|
|
let link = document.querySelector("link[rel*='icon']");
|
|
if (!link) {
|
|
link = document.createElement('link');
|
|
link.rel = 'icon';
|
|
document.head.appendChild(link);
|
|
}
|
|
link.href = nextLogo;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('site-config-updated', handleSiteConfigUpdated);
|
|
return () => window.removeEventListener('site-config-updated', handleSiteConfigUpdated);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const checkAuth = async () => {
|
|
markDesktopEmbedSession();
|
|
const token = localStorage.getItem('token');
|
|
const fallbackUser = buildFallbackUserFromToken(token);
|
|
|
|
if (canRestoreDesktopSessionImmediately(isDesktopEmbedSession(), fallbackUser)) {
|
|
setUser(fallbackUser);
|
|
setAuthReady(true);
|
|
|
|
try {
|
|
const verification = await verifyToken();
|
|
if (!verification?.valid || !verification.user) {
|
|
clearAuthSession();
|
|
requestDesktopSessionRenewal('web-session-invalid');
|
|
return;
|
|
}
|
|
|
|
const resolvedUser = verification.user;
|
|
localStorage.setItem(AUTH_USER_STORAGE_KEY, JSON.stringify(resolvedUser));
|
|
setUser(resolvedUser);
|
|
|
|
if (resolvedUser.role === 'admin') {
|
|
const setup = await getSetupStatus().catch((error) => {
|
|
console.warn('Setup status unavailable, continue with current session:', error);
|
|
return null;
|
|
});
|
|
if (setup?.shouldShowWizard) {
|
|
navigate('/setup', { replace: true });
|
|
}
|
|
}
|
|
} catch (error) {
|
|
if (!canFallbackToJwtPayload(error, fallbackUser)) {
|
|
clearAuthSession();
|
|
requestDesktopSessionRenewal('web-session-invalid');
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const installStatusPromise = getInstallStatus().catch(() => ({ required: false }));
|
|
const verificationPromise = verifyToken()
|
|
.then((res) => ({ res, error: null }))
|
|
.catch((error) => ({ res: null, error }));
|
|
const setupStatusPromise = fallbackUser?.role === 'admin'
|
|
? getSetupStatus().catch((error) => {
|
|
console.warn('Setup status unavailable, continue with current session:', error);
|
|
return null;
|
|
})
|
|
: null;
|
|
const [installStatus, verification] = await Promise.all([
|
|
installStatusPromise,
|
|
verificationPromise,
|
|
]);
|
|
if (installStatus?.required && !fallbackUser) {
|
|
clearAuthSession();
|
|
navigate('/install', { replace: true });
|
|
return;
|
|
}
|
|
|
|
let resolvedUser = null;
|
|
const verifyError = verification.error;
|
|
if (verification.res?.valid) {
|
|
resolvedUser = verification.res.user;
|
|
} else if (verifyError) {
|
|
console.warn('Token verification failed:', verifyError);
|
|
}
|
|
|
|
if (!resolvedUser && canFallbackToJwtPayload(verifyError, fallbackUser)) {
|
|
resolvedUser = fallbackUser;
|
|
}
|
|
|
|
if (resolvedUser) {
|
|
localStorage.setItem(AUTH_USER_STORAGE_KEY, JSON.stringify(resolvedUser));
|
|
if (resolvedUser.role === 'admin') {
|
|
try {
|
|
const setup = setupStatusPromise
|
|
? await setupStatusPromise
|
|
: await getSetupStatus();
|
|
if (setup?.shouldShowWizard) {
|
|
navigate('/setup', { replace: true });
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
console.warn('Setup status unavailable, continue with current session:', error);
|
|
}
|
|
}
|
|
|
|
setUser(resolvedUser);
|
|
} else if (installStatus?.required) {
|
|
clearAuthSession();
|
|
navigate('/install', { replace: true });
|
|
} else {
|
|
clearAuthSession();
|
|
if (!requestDesktopSessionRenewal('web-session-missing')) {
|
|
navigate('/login');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (fallbackUser) {
|
|
setUser(fallbackUser);
|
|
} else {
|
|
clearAuthSession();
|
|
if (!requestDesktopSessionRenewal('web-session-invalid')) {
|
|
navigate('/login');
|
|
}
|
|
}
|
|
} finally {
|
|
setAuthReady(true);
|
|
}
|
|
};
|
|
|
|
checkAuth();
|
|
}, [navigate]);
|
|
|
|
useEffect(() => {
|
|
if (!authReady || !user) {
|
|
return;
|
|
}
|
|
|
|
if (pathname === '/' || pathname === '/dashboard') {
|
|
navigate(PLANNING_HOME_URL, { replace: true });
|
|
return;
|
|
}
|
|
|
|
if (pathname.startsWith('/settings') && !canUseSettings) {
|
|
navigate(PLANNING_HOME_URL, { replace: true });
|
|
return;
|
|
}
|
|
|
|
const isAllowedPath = (
|
|
pathname === '/planning'
|
|
|| pathname.startsWith('/planning/')
|
|
|| pathname === '/settings'
|
|
|| pathname.startsWith('/settings/')
|
|
);
|
|
|
|
if (!isAllowedPath) {
|
|
navigate(PLANNING_HOME_URL, { replace: true });
|
|
}
|
|
}, [authReady, canUseSettings, navigate, pathname, user]);
|
|
|
|
const handleLogout = () => {
|
|
clearAuthSession();
|
|
navigate('/login');
|
|
};
|
|
|
|
const userMenu = [
|
|
canUseSettings
|
|
? {
|
|
key: 'settings',
|
|
icon: <SettingOutlined />,
|
|
label: '系统设置',
|
|
onClick: () => navigate('/settings'),
|
|
}
|
|
: null,
|
|
{
|
|
key: 'logout',
|
|
icon: <LogoutOutlined />,
|
|
label: '退出登录',
|
|
onClick: handleLogout,
|
|
},
|
|
].filter(Boolean);
|
|
|
|
const isSidebarEntryActive = (entry) => {
|
|
if (!entry) return false;
|
|
if (entry.match?.(pathname)) return true;
|
|
return Array.isArray(entry.children) && entry.children.some((child) => isSidebarEntryActive(child));
|
|
};
|
|
|
|
const toggleSidebarGroup = (key) => {
|
|
setSidebarCollapsed(false);
|
|
setExpandedSidebarKeys((keys) => (
|
|
keys.includes(key)
|
|
? keys.filter((item) => item !== key)
|
|
: [...keys, key]
|
|
));
|
|
};
|
|
|
|
const handleSidebarLeafClick = (item) => {
|
|
if (item.external) {
|
|
item.onClick?.();
|
|
return;
|
|
}
|
|
if (item.path) {
|
|
navigate(item.path);
|
|
}
|
|
};
|
|
|
|
const handleSidebarDarkModeChange = (checked) => {
|
|
setSidebarDarkMode(checked);
|
|
try {
|
|
localStorage.setItem(SIDEBAR_DARK_MODE_STORAGE_KEY, checked ? 'dark' : 'light');
|
|
} catch {
|
|
// Keep the current-session preference when persistence is unavailable.
|
|
}
|
|
};
|
|
|
|
if (!authReady) {
|
|
return <AppShellSkeleton />;
|
|
}
|
|
|
|
return (
|
|
<div className={`planning-subsystem-shell${isImmersivePlanningRoute ? ' is-immersive' : ''}${sidebarCollapsed ? ' is-sidebar-collapsed' : ''}${sidebarDarkMode ? ' is-sidebar-dark' : ''}`}>
|
|
<aside className="planning-subsystem-sidebar">
|
|
<button
|
|
type="button"
|
|
className="planning-subsystem-brand"
|
|
onClick={() => { window.location.href = BRAND_HOME_URL; }}
|
|
title="返回主系统"
|
|
>
|
|
<span className="planning-subsystem-brand-logo">
|
|
{siteConfig.logo ? <img src={siteConfig.logo} alt="" /> : <CloudOutlined />}
|
|
</span>
|
|
<span className="planning-subsystem-brand-title">{siteConfig.siteName || FALLBACK_SITE_NAME}</span>
|
|
</button>
|
|
|
|
<nav className="planning-subsystem-nav" aria-label="主导航">
|
|
{sidebarSections.map((item) => {
|
|
if (item.type === 'section') {
|
|
return (
|
|
<div key={item.key} className="planning-subsystem-nav-section">
|
|
{item.label}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Icon = item.icon;
|
|
const expandable = Array.isArray(item.children) && item.children.length > 0;
|
|
const descendantActive = expandable && item.children.some((child) => isSidebarEntryActive(child));
|
|
const selfActive = Boolean(item.match?.(pathname));
|
|
const active = selfActive || (sidebarCollapsed && descendantActive);
|
|
const expanded = expandable && expandedSidebarKeys.includes(item.key) && !sidebarCollapsed;
|
|
|
|
return (
|
|
<div key={item.key} className={`planning-subsystem-nav-group${expanded ? ' is-expanded' : ''}`}>
|
|
<button
|
|
type="button"
|
|
className={`planning-subsystem-nav-item${active ? ' is-active' : ''}${descendantActive ? ' is-descendant-active' : ''}${expandable ? ' is-expandable' : ''}`}
|
|
onClick={() => {
|
|
if (expandable) {
|
|
toggleSidebarGroup(item.key);
|
|
return;
|
|
}
|
|
handleSidebarLeafClick(item);
|
|
}}
|
|
title={item.label}
|
|
aria-expanded={expandable ? expanded : undefined}
|
|
>
|
|
<Icon className="planning-subsystem-nav-icon" />
|
|
<span className="planning-subsystem-nav-label">{item.label}</span>
|
|
{expandable ? (
|
|
<span className="planning-subsystem-nav-chevron">
|
|
{expanded ? <DownOutlined /> : <RightOutlined />}
|
|
</span>
|
|
) : null}
|
|
</button>
|
|
|
|
{expandable && expanded ? (
|
|
<div className="planning-subsystem-subnav">
|
|
{item.children.map((child) => {
|
|
const ChildIcon = child.icon;
|
|
const childActive = isSidebarEntryActive(child);
|
|
return (
|
|
<button
|
|
key={child.key}
|
|
type="button"
|
|
className={`planning-subsystem-subnav-item${childActive ? ' is-active' : ''}`}
|
|
onClick={() => handleSidebarLeafClick(child)}
|
|
title={child.label}
|
|
>
|
|
<ChildIcon className="planning-subsystem-subnav-icon" />
|
|
<span>{child.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="planning-subsystem-sidebar-bottom">
|
|
<div className="planning-subsystem-sidebar-separator" />
|
|
<div className="planning-subsystem-theme-row" title={sidebarDarkMode ? '关闭侧栏暗黑模式' : '开启侧栏暗黑模式'}>
|
|
{sidebarDarkMode ? <MoonOutlined /> : <SunOutlined />}
|
|
<span className="planning-subsystem-theme-label">暗黑模式</span>
|
|
<Switch
|
|
size="small"
|
|
checked={sidebarDarkMode}
|
|
onChange={handleSidebarDarkModeChange}
|
|
aria-label="侧栏暗黑模式"
|
|
/>
|
|
</div>
|
|
<div className="planning-subsystem-account-row">
|
|
<button
|
|
type="button"
|
|
className="planning-subsystem-collapse"
|
|
onClick={() => setSidebarCollapsed((value) => !value)}
|
|
title={sidebarCollapsed ? '展开侧边栏' : '收起侧边栏'}
|
|
>
|
|
{sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
<span className="planning-subsystem-collapse-label">{sidebarCollapsed ? '展开' : '收起'}</span>
|
|
</button>
|
|
|
|
<Dropdown menu={{ items: userMenu }} trigger={['click']} placement="topRight">
|
|
<button type="button" className="planning-subsystem-user" title={userDisplayName}>
|
|
<img src={userAvatar} alt={userDisplayName} />
|
|
<span className="planning-subsystem-user-copy">
|
|
<strong>{userDisplayName}</strong>
|
|
</span>
|
|
<span className="planning-subsystem-user-more" aria-hidden="true">
|
|
<span />
|
|
<span />
|
|
<span />
|
|
</span>
|
|
</button>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<main className={`planning-subsystem-content${isImmersivePlanningRoute ? ' is-immersive' : ''}`}>
|
|
<ErrorBoundary key={location.pathname}>
|
|
<Outlet context={{ sidebarCollapsed, setSidebarCollapsed }} />
|
|
</ErrorBoundary>
|
|
</main>
|
|
|
|
<style>{`
|
|
.planning-subsystem-shell {
|
|
--sidebar-width: var(--sycm-sidebar-width);
|
|
--sidebar-collapsed-width: var(--sycm-sidebar-collapsed-width);
|
|
--sidebar-bg: var(--sycm-sidebar-bg);
|
|
--sidebar-border: var(--sycm-sidebar-border);
|
|
--sidebar-active-bg: var(--sycm-sidebar-active-bg);
|
|
--sidebar-active-text: var(--sycm-sidebar-active-text);
|
|
--sidebar-hover-bg: var(--sycm-sidebar-hover-bg);
|
|
--sidebar-icon: var(--sycm-sidebar-icon-active);
|
|
--sidebar-icon-muted: var(--sycm-sidebar-icon);
|
|
--sidebar-text: var(--sycm-sidebar-text);
|
|
--sidebar-muted: var(--sycm-sidebar-muted);
|
|
--sidebar-section: var(--sycm-sidebar-tertiary);
|
|
--sidebar-accent: var(--sycm-sidebar-accent);
|
|
display: flex;
|
|
min-height: 100vh;
|
|
background: var(--sycm-bg-page-surface);
|
|
font-family: var(--sycm-font-family);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-immersive {
|
|
background: var(--sycm-bg-page-surface);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-dark {
|
|
--sidebar-bg: var(--sycm-sidebar-dark-bg);
|
|
--sidebar-border: var(--sycm-sidebar-dark-border);
|
|
--sidebar-active-bg: var(--sycm-sidebar-dark-active-bg);
|
|
--sidebar-active-text: var(--sycm-sidebar-dark-active-text);
|
|
--sidebar-hover-bg: var(--sycm-sidebar-dark-hover-bg);
|
|
--sidebar-icon: var(--sycm-sidebar-dark-icon-active);
|
|
--sidebar-icon-muted: var(--sycm-sidebar-dark-icon);
|
|
--sidebar-text: var(--sycm-sidebar-dark-text);
|
|
--sidebar-muted: var(--sycm-sidebar-dark-muted);
|
|
--sidebar-section: var(--sycm-sidebar-dark-tertiary);
|
|
--sidebar-accent: var(--sycm-sidebar-dark-accent);
|
|
}
|
|
|
|
.planning-subsystem-sidebar {
|
|
position: sticky;
|
|
top: 0;
|
|
display: flex;
|
|
width: var(--sidebar-width);
|
|
height: 100vh;
|
|
flex: 0 0 var(--sidebar-width);
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
padding: var(--sycm-space-md) var(--sycm-space-xs) var(--sycm-space-sm);
|
|
border-right: 1px solid var(--sidebar-border);
|
|
background: var(--sidebar-bg);
|
|
box-shadow: var(--sycm-shadow-card);
|
|
transition:
|
|
width 0.25s ease,
|
|
flex-basis 0.25s ease,
|
|
padding 0.25s ease;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-sidebar {
|
|
width: var(--sidebar-collapsed-width);
|
|
flex-basis: var(--sidebar-collapsed-width);
|
|
align-items: center;
|
|
padding: var(--sycm-space-md) var(--sycm-space-xs) var(--sycm-space-sm);
|
|
}
|
|
|
|
.planning-subsystem-brand {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
width: 100%;
|
|
min-height: 52px;
|
|
justify-content: flex-start;
|
|
gap: 12px;
|
|
margin-bottom: var(--sycm-space-sm);
|
|
padding: 0 10px;
|
|
border: 0;
|
|
border-radius: var(--sycm-radius-sm);
|
|
background: transparent;
|
|
color: var(--sidebar-text);
|
|
font-family: inherit;
|
|
font-size: var(--sycm-font-size-title);
|
|
font-weight: var(--sycm-font-weight-semibold);
|
|
line-height: var(--sycm-line-height-tight);
|
|
letter-spacing: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.planning-subsystem-brand-logo {
|
|
display: inline-flex;
|
|
width: 32px;
|
|
height: 32px;
|
|
flex: 0 0 32px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
border-radius: var(--sycm-radius-sm);
|
|
color: var(--sidebar-accent);
|
|
font-size: 24px;
|
|
}
|
|
|
|
.planning-subsystem-brand-logo img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
display: block;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-dark .planning-subsystem-brand-logo {
|
|
padding: 4px;
|
|
background: rgba(255, 255, 255, 0.94);
|
|
}
|
|
|
|
.planning-subsystem-brand-title {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.planning-subsystem-nav {
|
|
display: flex;
|
|
min-height: 0;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
overflow: auto;
|
|
padding: 0;
|
|
}
|
|
|
|
.planning-subsystem-nav::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.planning-subsystem-nav-group {
|
|
display: flex;
|
|
width: 100%;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.planning-subsystem-nav-section {
|
|
height: 18px;
|
|
margin: 10px 8px 2px;
|
|
color: var(--sidebar-section);
|
|
font-size: var(--sycm-font-size-caption);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
line-height: 18px;
|
|
letter-spacing: 0;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.planning-subsystem-nav-item {
|
|
position: relative;
|
|
display: flex;
|
|
width: 100%;
|
|
height: 36px;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: 10px;
|
|
padding: 0 10px;
|
|
border: 0;
|
|
border-radius: var(--sycm-radius-sm);
|
|
background: transparent;
|
|
color: var(--sidebar-text);
|
|
font-family: inherit;
|
|
font-size: var(--sycm-font-size-body);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
line-height: var(--sycm-line-height-tight);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: color 0.18s ease;
|
|
}
|
|
|
|
.planning-subsystem-nav-item::before,
|
|
.planning-subsystem-subnav-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
z-index: 0;
|
|
inset: 0 24px 0 -8px;
|
|
border-radius: 0 var(--sycm-radius-pill) var(--sycm-radius-pill) 0;
|
|
background: transparent;
|
|
pointer-events: none;
|
|
transition: background-color 0.18s ease;
|
|
}
|
|
|
|
.planning-subsystem-nav-item > *,
|
|
.planning-subsystem-subnav-item > * {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.planning-subsystem-nav-item:focus,
|
|
.planning-subsystem-subnav-item:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.planning-subsystem-nav-item:focus-visible::before,
|
|
.planning-subsystem-subnav-item:focus-visible::before {
|
|
box-shadow: inset 0 0 0 var(--sycm-line-width-base) var(--sycm-color-primary-border);
|
|
}
|
|
|
|
.planning-subsystem-nav-item:hover {
|
|
color: var(--sidebar-text);
|
|
}
|
|
|
|
.planning-subsystem-nav-item:hover::before {
|
|
background: var(--sidebar-hover-bg);
|
|
}
|
|
|
|
.planning-subsystem-nav-item.is-active {
|
|
color: var(--sidebar-active-text);
|
|
font-weight: var(--sycm-font-weight-semibold);
|
|
box-shadow: none;
|
|
}
|
|
|
|
.planning-subsystem-nav-item.is-active::before {
|
|
background: var(--sidebar-active-bg);
|
|
}
|
|
|
|
.planning-subsystem-nav-item.is-descendant-active:not(.is-active) {
|
|
color: var(--sidebar-active-text);
|
|
}
|
|
|
|
.planning-subsystem-nav-icon {
|
|
flex: 0 0 17px;
|
|
color: var(--sidebar-icon-muted);
|
|
font-size: 15px;
|
|
}
|
|
|
|
.planning-subsystem-nav-item.is-active .planning-subsystem-nav-icon,
|
|
.planning-subsystem-nav-item.is-descendant-active .planning-subsystem-nav-icon,
|
|
.planning-subsystem-nav-item:hover .planning-subsystem-nav-icon {
|
|
color: var(--sidebar-icon);
|
|
}
|
|
|
|
.planning-subsystem-nav-label {
|
|
min-width: 0;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.planning-subsystem-nav-chevron {
|
|
display: inline-flex;
|
|
width: 12px;
|
|
height: 12px;
|
|
flex: 0 0 12px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-left: auto;
|
|
color: var(--sidebar-section);
|
|
font-size: var(--sycm-font-size-micro);
|
|
line-height: 1;
|
|
}
|
|
|
|
.planning-subsystem-subnav {
|
|
display: flex;
|
|
width: 100%;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
margin: 0 0 4px;
|
|
}
|
|
|
|
.planning-subsystem-subnav-item {
|
|
position: relative;
|
|
display: flex;
|
|
width: 100%;
|
|
height: var(--sycm-control-height-sm);
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: 9px;
|
|
padding: 0 8px 0 36px;
|
|
border: 0;
|
|
border-radius: var(--sycm-radius-sm);
|
|
background: transparent;
|
|
color: var(--sidebar-muted);
|
|
font-family: inherit;
|
|
font-size: var(--sycm-font-size-caption);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
line-height: 18px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: color 0.18s ease;
|
|
}
|
|
|
|
.planning-subsystem-subnav-item:hover {
|
|
color: var(--sidebar-text);
|
|
}
|
|
|
|
.planning-subsystem-subnav-item:hover::before {
|
|
background: var(--sidebar-hover-bg);
|
|
}
|
|
|
|
.planning-subsystem-subnav-item.is-active {
|
|
color: var(--sidebar-active-text);
|
|
font-weight: var(--sycm-font-weight-semibold);
|
|
}
|
|
|
|
.planning-subsystem-subnav-item.is-active::before {
|
|
background: var(--sidebar-active-bg);
|
|
}
|
|
|
|
.planning-subsystem-subnav-icon {
|
|
flex: 0 0 14px;
|
|
color: var(--sidebar-icon-muted);
|
|
font-size: var(--sycm-font-size-caption);
|
|
}
|
|
|
|
.planning-subsystem-subnav-item:hover .planning-subsystem-subnav-icon,
|
|
.planning-subsystem-subnav-item.is-active .planning-subsystem-subnav-icon {
|
|
color: var(--sidebar-icon);
|
|
}
|
|
|
|
.planning-subsystem-nav-badge {
|
|
display: inline-flex;
|
|
min-width: 18px;
|
|
height: 18px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: var(--sycm-radius-pill);
|
|
padding: 0 5px;
|
|
background: var(--sycm-bg-subtle);
|
|
color: var(--sycm-text-secondary);
|
|
font-size: var(--sycm-font-size-caption);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
box-shadow: none;
|
|
}
|
|
|
|
.planning-subsystem-sidebar-bottom {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.planning-subsystem-sidebar-separator {
|
|
height: 1px;
|
|
margin: 6px 0;
|
|
background: var(--sidebar-border);
|
|
}
|
|
|
|
.planning-subsystem-theme-row {
|
|
display: flex;
|
|
width: 100%;
|
|
height: var(--sycm-control-height);
|
|
align-items: center;
|
|
gap: var(--sycm-space-xs);
|
|
padding: 0 8px;
|
|
border-radius: var(--sycm-radius-sm);
|
|
color: var(--sidebar-muted);
|
|
font-size: var(--sycm-font-size-caption);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
}
|
|
|
|
.planning-subsystem-theme-row:hover {
|
|
background: var(--sidebar-hover-bg);
|
|
color: var(--sidebar-text);
|
|
}
|
|
|
|
.planning-subsystem-theme-row > .anticon {
|
|
flex: 0 0 16px;
|
|
color: var(--sidebar-icon-muted);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.planning-subsystem-theme-label {
|
|
min-width: 0;
|
|
flex: 1;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.planning-subsystem-theme-row .ant-switch {
|
|
flex: 0 0 auto;
|
|
background: var(--sidebar-border);
|
|
}
|
|
|
|
.planning-subsystem-theme-row .ant-switch.ant-switch-checked {
|
|
background: var(--sidebar-accent);
|
|
}
|
|
|
|
.planning-subsystem-account-row {
|
|
display: grid;
|
|
width: 100%;
|
|
min-width: 0;
|
|
grid-template-columns: auto minmax(0, 1fr);
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.planning-subsystem-collapse {
|
|
display: flex;
|
|
width: auto;
|
|
height: var(--sycm-control-height-sm);
|
|
flex: 0 0 auto;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: var(--sycm-space-xs);
|
|
padding: 0 8px;
|
|
border: 0;
|
|
border-radius: var(--sycm-radius-sm);
|
|
background: transparent;
|
|
color: var(--sidebar-muted);
|
|
font-family: inherit;
|
|
font-size: var(--sycm-font-size-caption);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.planning-subsystem-collapse:hover {
|
|
background: var(--sidebar-hover-bg);
|
|
color: var(--sidebar-text);
|
|
}
|
|
|
|
.planning-subsystem-collapse .anticon {
|
|
flex: 0 0 16px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.planning-subsystem-user {
|
|
display: flex;
|
|
width: 100%;
|
|
min-width: 0;
|
|
min-height: var(--sycm-control-height-sm);
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-top: 0;
|
|
padding: 0 8px;
|
|
border: 0;
|
|
border-radius: var(--sycm-radius-sm);
|
|
background: transparent;
|
|
color: var(--sidebar-text);
|
|
font-family: inherit;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.planning-subsystem-user:hover {
|
|
background: var(--sidebar-hover-bg);
|
|
}
|
|
|
|
.planning-subsystem-user img {
|
|
width: 20px;
|
|
height: 20px;
|
|
flex: 0 0 20px;
|
|
border-radius: var(--sycm-radius-pill);
|
|
object-fit: cover;
|
|
background: var(--sycm-bg-subtle);
|
|
}
|
|
|
|
.planning-subsystem-user-copy {
|
|
display: block;
|
|
min-width: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.planning-subsystem-user-copy strong {
|
|
overflow: hidden;
|
|
color: var(--sidebar-text);
|
|
font-size: var(--sycm-font-size-caption);
|
|
font-weight: var(--sycm-font-weight-medium);
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.planning-subsystem-user-more {
|
|
display: inline-flex;
|
|
width: 4px;
|
|
flex: 0 0 4px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
gap: 3px;
|
|
color: var(--sidebar-muted);
|
|
}
|
|
|
|
.planning-subsystem-user-more span {
|
|
width: 3px;
|
|
height: 3px;
|
|
border-radius: var(--sycm-radius-pill);
|
|
background: currentColor;
|
|
}
|
|
|
|
.planning-subsystem-content {
|
|
min-width: 0;
|
|
height: 100vh;
|
|
flex: 1;
|
|
overflow: auto;
|
|
padding: 0;
|
|
background: var(--sycm-bg-page-surface);
|
|
}
|
|
|
|
.planning-subsystem-content.is-immersive {
|
|
height: 100vh;
|
|
padding: 0;
|
|
}
|
|
|
|
.planning-subsystem-content::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
.planning-subsystem-content::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.planning-subsystem-content::-webkit-scrollbar-thumb {
|
|
background: var(--sycm-border-strong);
|
|
border-radius: var(--sycm-radius-pill);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-brand,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-item,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-theme-row,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-collapse,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-user {
|
|
justify-content: center;
|
|
padding-right: 0;
|
|
padding-left: 0;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-brand-title,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-label,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-badge,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-chevron,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-section,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-subnav,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-theme-label,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-collapse-label,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-user-copy,
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-user-more {
|
|
display: none;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-item {
|
|
width: 36px;
|
|
margin-left: 0;
|
|
border-radius: var(--sycm-radius-pill);
|
|
}
|
|
|
|
|
|
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-nav-item::before {
|
|
inset: 0;
|
|
border-radius: var(--sycm-radius-pill);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-theme-row {
|
|
width: 36px;
|
|
min-width: 36px;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-account-row {
|
|
display: flex;
|
|
width: 36px;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-theme-row > .anticon {
|
|
display: none;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-collapse {
|
|
width: 36px;
|
|
min-width: 36px;
|
|
color: var(--sidebar-icon-muted);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-collapse:hover {
|
|
color: var(--sidebar-icon);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-collapse .anticon {
|
|
display: inline-flex;
|
|
flex: 0 0 16px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: currentColor;
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-sidebar-separator {
|
|
width: var(--sycm-control-height-sm);
|
|
}
|
|
|
|
.planning-subsystem-shell.is-sidebar-collapsed .planning-subsystem-user img {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.planning-subsystem-shell {
|
|
--sidebar-width: var(--sycm-sidebar-collapsed-width);
|
|
}
|
|
|
|
.planning-subsystem-sidebar {
|
|
width: var(--sidebar-width);
|
|
flex-basis: var(--sidebar-width);
|
|
align-items: center;
|
|
padding: var(--sycm-space-md) var(--sycm-space-xs) var(--sycm-space-sm);
|
|
}
|
|
|
|
.planning-subsystem-content {
|
|
height: 100vh;
|
|
}
|
|
|
|
.planning-subsystem-content.is-immersive {
|
|
height: 100vh;
|
|
}
|
|
|
|
.planning-subsystem-nav-item,
|
|
.planning-subsystem-nav-item:hover,
|
|
.planning-subsystem-nav-item.is-active {
|
|
width: 36px;
|
|
margin-left: 0;
|
|
padding-right: 0;
|
|
padding-left: 0;
|
|
border-radius: var(--sycm-radius-pill);
|
|
justify-content: center;
|
|
}
|
|
|
|
.planning-subsystem-nav-item::before {
|
|
inset: 0;
|
|
border-radius: var(--sycm-radius-pill);
|
|
}
|
|
|
|
|
|
|
|
|
|
.planning-subsystem-brand-title,
|
|
.planning-subsystem-nav-label,
|
|
.planning-subsystem-nav-badge,
|
|
.planning-subsystem-nav-chevron,
|
|
.planning-subsystem-nav-section,
|
|
.planning-subsystem-subnav,
|
|
.planning-subsystem-theme-label,
|
|
.planning-subsystem-collapse-label,
|
|
.planning-subsystem-user-copy,
|
|
.planning-subsystem-user-more {
|
|
display: none;
|
|
}
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BasicLayout;
|