chore: delete old layout/common/tabs components before redesign
@
This commit is contained in:
2026-06-03 16:58:35 +08:00
parent f3dbdc7e3f
commit dcda7e0423
53 changed files with 24412 additions and 1519 deletions

View File

@@ -1,15 +0,0 @@
import React from 'react';
interface TLogoProps {
size?: number;
}
export const TLogo: React.FC<TLogoProps> = ({ size = 40 }) => (
<img
src="/logo/t_mobile_logo_transparent.png"
alt="T-Systems"
width={size}
height={size}
style={{ objectFit: 'contain' }}
/>
);

View File

@@ -1,30 +0,0 @@
import React from 'react';
import { useTheme } from '../../contexts';
export const TPattern: React.FC = () => {
const { theme, isDark } = useTheme();
const patternOpacity = isDark ? 0.03 : 0.04;
return (
<div
style={{
position: 'absolute',
top: 0,
right: 0,
width: 300,
height: 300,
opacity: patternOpacity,
pointerEvents: 'none',
}}
>
<svg width="300" height="300" viewBox="0 0 300 300">
<defs>
<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
<path d="M 30 0 L 0 0 0 30" fill="none" stroke={theme.accent} strokeWidth="1"/>
</pattern>
</defs>
<rect width="300" height="300" fill="url(#grid)"/>
</svg>
</div>
);
};

View File

@@ -1,34 +0,0 @@
import React from 'react';
import { Moon, Sun, SunMedium } from 'lucide-react';
import { useTheme } from '../../contexts';
import { Button } from '../shadcn/ui/button';
const NEXT_LABELS: Record<string, string> = {
dark: '过渡色模式',
dim: '亮色模式',
light: '暗色模式',
};
export const ThemeToggle: React.FC = () => {
const { themeMode, toggleTheme } = useTheme();
// Shows the NEXT state's icon: dark→SunMedium(dim next), dim→Sun(light next), light→Moon(dark next)
const Icon =
themeMode === 'dark' ? SunMedium :
themeMode === 'dim' ? Sun :
Moon;
return (
<Button
onClick={toggleTheme}
variant="outline"
size="icon-lg"
className="rounded-xl border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground"
aria-label={`切换到${NEXT_LABELS[themeMode]}`}
title={`切换到${NEXT_LABELS[themeMode]}`}
>
<Icon />
</Button>
);
};

View File

@@ -1,40 +0,0 @@
import type { ReactNode } from 'react';
import type { AppTabConfig } from '../../router/tabs';
import { shellFrameClassName } from './shell-config';
interface ContentLayoutProps {
children: ReactNode;
tab: AppTabConfig;
}
const widthClassMap = {
default: 'mx-auto w-full max-w-[1120px]',
wide: 'mx-auto w-full max-w-[1440px]',
full: 'w-full',
} as const;
export function ContentLayout({ children, tab }: ContentLayoutProps) {
const widthClass = widthClassMap[tab.contentWidth];
return (
<main className="flex min-h-0 flex-1 bg-t-bg">
<div
className={[
shellFrameClassName,
'relative flex min-h-0 flex-1 justify-center py-8',
].join(' ')}
>
<div
className={[
'relative flex min-h-0 w-full',
widthClass,
tab.fillHeight ? 'overflow-hidden' : '',
].join(' ')}
>
{children}
</div>
</div>
</main>
);
}

View File

@@ -1,38 +0,0 @@
import { Badge } from '../shadcn/ui/badge';
import { Separator } from '../shadcn/ui/separator';
import { shellFrameClassName, shellMeta } from './shell-config';
export function FooterLayout() {
return (
<footer className="border-t border-t-border bg-t-bg">
<div
className={[
shellFrameClassName,
'flex items-center justify-between gap-6 py-4 text-xs text-t-text3',
].join(' ')}
>
<div className="min-w-0 max-w-[360px]">
<div className="mono mb-1 tracking-[0.18em] text-t-text2">
{shellMeta.productLabel}
</div>
</div>
<div className="flex shrink-0 items-center gap-3 whitespace-nowrap rounded-xl border border-border bg-card px-3 py-2 shadow-sm">
<Badge variant="secondary" className="mono border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-muted-foreground">
{shellMeta.version}
</Badge>
<Separator orientation="vertical" className="h-4 bg-border" />
<Badge variant="outline" className="mono gap-2 border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-[var(--t-green)]">
<span className="size-2 rounded-full bg-[var(--t-green)]" />
{shellMeta.status}
</Badge>
<Separator orientation="vertical" className="h-4 bg-border" />
<span className="mono text-[11px] tracking-[0.18em] text-muted-foreground">
{shellMeta.surface}
</span>
</div>
</div>
</footer>
);
}

View File

@@ -1,19 +0,0 @@
import { TLogo } from '../common/TLogo';
export function HeaderBrand() {
return (
<div className="flex min-w-[280px] shrink-0 items-center gap-4 whitespace-nowrap">
<div className="shrink-0">
<TLogo size={46} />
</div>
<div className="flex min-w-0 items-center gap-2 whitespace-nowrap">
<span className="text-[1.18rem] font-semibold tracking-[-0.04em] text-foreground">
T-Systems
</span>
<span className="text-[1.02rem] font-light text-muted-foreground">
Regulation
</span>
</div>
</div>
);
}

View File

@@ -1,38 +0,0 @@
import type { AppTabConfig } from '../../router/tabs';
import { ThemeToggle } from '../common/ThemeToggle';
import { Badge } from '../shadcn/ui/badge';
import { Separator } from '../shadcn/ui/separator';
import { HeaderBrand } from './HeaderBrand';
import { shellFrameClassName, shellMeta } from './shell-config';
import { TabNav } from './TabNav';
interface HeaderLayoutProps {
activeTab: AppTabConfig;
}
export function HeaderLayout({ activeTab }: HeaderLayoutProps) {
return (
<header className="sticky top-0 z-[100] border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80">
<div className={[shellFrameClassName, 'flex h-20 items-center gap-8'].join(' ')}>
<HeaderBrand />
<div className="min-w-0 flex-1 self-stretch overflow-hidden">
<TabNav activeTab={activeTab} />
</div>
<div className="ml-auto flex shrink-0 items-center gap-3 self-center">
<ThemeToggle />
<div className="flex h-11 shrink-0 items-center gap-3 whitespace-nowrap rounded-xl border border-border bg-card px-3 shadow-sm">
<Badge variant="secondary" className="mono border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-muted-foreground">
{shellMeta.version}
</Badge>
<Separator orientation="vertical" className="h-4 bg-border" />
<Badge variant="outline" className="mono gap-2 border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-[var(--t-green)]">
<span className="size-2 rounded-full bg-[var(--t-green)]" />
{shellMeta.status}
</Badge>
</div>
</div>
</div>
</header>
);
}

View File

@@ -1,45 +0,0 @@
import { useEffect, useState } from 'react';
import { appTabs, type AppTabConfig } from '../../router/tabs';
interface KeepAliveViewportProps {
activeTab: AppTabConfig;
}
export function KeepAliveViewport({ activeTab }: KeepAliveViewportProps) {
const [mountedTabIds, setMountedTabIds] = useState<string[]>([activeTab.id]);
useEffect(() => {
const timerId = window.setTimeout(() => {
setMountedTabIds((prev) => (prev.includes(activeTab.id) ? prev : [...prev, activeTab.id]));
}, 0);
return () => window.clearTimeout(timerId);
}, [activeTab.id]);
return (
<div className="flex min-h-0 flex-1">
{appTabs.map((tab) => {
const shouldRender = tab.keepAlive ? mountedTabIds.includes(tab.id) : tab.id === activeTab.id;
if (!shouldRender) {
return null;
}
const TabComponent = tab.component;
const isActive = tab.id === activeTab.id;
return (
<div
key={tab.id}
aria-hidden={!isActive}
className={[
'min-h-0 flex-1',
isActive ? 'flex' : 'hidden',
].join(' ')}
>
<TabComponent />
</div>
);
})}
</div>
);
}

View File

@@ -1,125 +0,0 @@
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import type { AppTabConfig, TabId } from '../../router/tabs';
import { appTabs } from '../../router/tabs';
interface TabNavProps {
activeTab: AppTabConfig;
}
interface IndicatorStyle {
opacity: number;
transform: string;
width: number;
}
const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
export function TabNav({ activeTab }: TabNavProps) {
const navigate = useNavigate();
const trackRef = useRef<HTMLDivElement | null>(null);
const buttonRefs = useRef<Record<TabId, HTMLButtonElement | null>>({
perception: null,
docs: null,
compliance: null,
status: null,
rag: null,
});
const [indicatorStyle, setIndicatorStyle] = useState<IndicatorStyle>({
opacity: 0,
transform: 'translateX(0px)',
width: 0,
});
const [reducedMotion, setReducedMotion] = useState(false);
const handleValueChange = (value: string) => {
const nextTab = appTabs.find((tab) => tab.id === value);
if (nextTab && nextTab.path !== activeTab.path) {
navigate(nextTab.path);
}
};
useEffect(() => {
const mediaQuery = window.matchMedia(reducedMotionQuery);
const updateMotionPreference = () => {
setReducedMotion(mediaQuery.matches);
};
updateMotionPreference();
mediaQuery.addEventListener('change', updateMotionPreference);
return () => {
mediaQuery.removeEventListener('change', updateMotionPreference);
};
}, []);
useLayoutEffect(() => {
const updateIndicator = () => {
const trackNode = trackRef.current;
const activeNode = buttonRefs.current[activeTab.id];
if (!trackNode || !activeNode) {
return;
}
const trackRect = trackNode.getBoundingClientRect();
const activeRect = activeNode.getBoundingClientRect();
setIndicatorStyle({
opacity: 1,
transform: `translateX(${activeRect.left - trackRect.left}px)`,
width: activeRect.width,
});
};
updateIndicator();
window.addEventListener('resize', updateIndicator);
return () => {
window.removeEventListener('resize', updateIndicator);
};
}, [activeTab.id]);
return (
<nav className="flex h-full min-w-0 items-stretch overflow-x-auto overflow-y-hidden">
<div
ref={trackRef}
className="relative flex h-full min-w-max flex-nowrap items-stretch gap-3 pr-6"
>
<div
aria-hidden="true"
className={[
'pointer-events-none absolute bottom-0 left-0 h-0.5 rounded-full bg-primary',
reducedMotion
? 'transition-none'
: 'transition-[transform,width,opacity] duration-220 ease-[cubic-bezier(0.22,1,0.36,1)]',
].join(' ')}
style={indicatorStyle}
/>
{appTabs.map((tab) => (
<button
key={tab.id}
ref={(node) => {
buttonRefs.current[tab.id] = node;
}}
data-shell-tab="true"
type="button"
onClick={() => handleValueChange(tab.id satisfies TabId)}
aria-current={tab.id === activeTab.id ? 'page' : undefined}
className={[
'inline-flex h-full shrink-0 appearance-none items-center justify-center border-0 border-b-2 border-transparent bg-transparent px-5 pt-1 text-[0.95rem] font-medium tracking-[0.02em] outline-none',
reducedMotion
? 'transition-none'
: 'transition-[color,opacity] duration-200 ease-out',
tab.id === activeTab.id
? 'text-foreground'
: 'text-muted-foreground hover:text-foreground',
].join(' ')}
>
{tab.label}
</button>
))}
</div>
</nav>
);
}

View File

@@ -1,73 +0,0 @@
import type { ComponentType } from 'react';
import { CompliancePage } from '../pages/Compliance';
import { DocsPage } from '../pages/Docs';
import { PerceptionPage } from '../pages/Perception';
import { RagChatPage } from '../pages/RagChat';
import { StatusPage } from '../pages/Status';
export type TabId = 'perception' | 'docs' | 'compliance' | 'status' | 'rag';
export type ContentWidth = 'default' | 'wide' | 'full';
export interface AppTabConfig {
id: TabId;
path: string;
label: string;
component: ComponentType;
keepAlive: boolean;
contentWidth: ContentWidth;
fillHeight?: boolean;
}
export const appTabs: AppTabConfig[] = [
{
id: 'perception',
path: '/perception',
label: '智能感知',
component: PerceptionPage,
keepAlive: true,
contentWidth: 'wide',
fillHeight: true,
},
{
id: 'docs',
path: '/docs',
label: '文档管理',
component: DocsPage,
keepAlive: true,
contentWidth: 'default',
},
{
id: 'compliance',
path: '/compliance',
label: '合规分析',
component: CompliancePage,
keepAlive: true,
contentWidth: 'wide',
fillHeight: true,
},
{
id: 'status',
path: '/status',
label: '系统状态',
component: StatusPage,
keepAlive: true,
contentWidth: 'default',
},
{
id: 'rag',
path: '/rag',
label: '法规对话',
component: RagChatPage,
keepAlive: true,
contentWidth: 'wide',
fillHeight: true,
},
];
export const defaultTab = appTabs.find((tab) => tab.id === 'compliance') ?? appTabs[0];
export function getTabByPath(pathname: string): AppTabConfig {
return appTabs.find((tab) => tab.path === pathname) ?? defaultTab;
}

View File

@@ -411,7 +411,6 @@ body {
.docs-card { display: flex; flex-direction: column; gap: 0; }
.doc-row { display: flex; gap: 10px; padding: 10px 0; border-top: 1px solid var(--border); }
.doc-score { font-size: 11px; font-weight: 700; font-family: var(--font-mono); color: var(--success); width: 34px; flex-shrink: 0; padding-top: 1px; }
.doc-info {}
.doc-name { font-size: 12px; font-weight: 600; margin-bottom: 3px; }
.doc-clause { font-size: 10px; font-family: var(--font-mono); color: var(--muted); margin-left: 5px; }
.doc-snippet { font-size: 11px; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }