Files
ai-platform/src/pages/HomePage.tsx
T
2026-08-27 16:37:33 +08:00

359 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { BrainCircuit, Calculator, Network, Boxes, ReceiptText, CalendarClock, Sparkles, LogOut } from 'lucide-react';
import { useTheme } from '@/shared/context/ThemeContext';
import StarBackground from '@/shared/components/StarBackground';
import ThemeToggle from '@/shared/components/ThemeToggle';
import { modules } from '@/modules/registry';
import { getUserInfo, clearSession, type SessionUserInfo } from '@/shared/services/iamAuth';
const ICON_MAP: Record<string, React.ComponentType<{ size?: number; color?: string }>> = {
BrainCircuit,
Calculator,
Network,
Boxes,
ReceiptText,
CalendarClock,
Sparkles,
};
export default function HomePage() {
const navigate = useNavigate();
const { theme } = useTheme();
const isDark = theme === 'dark';
const [searchParams] = useSearchParams();
const [userInfo, setUserInfo] = useState<SessionUserInfo | null>(() => getUserInfo());
// 登录守卫:未登录跳转 /login
useEffect(() => {
const token = sessionStorage.getItem('userToken');
if (!token) {
navigate('/login', { replace: true });
}
}, [navigate]);
// 首页模块多时需纵向滚动;离开后恢复全局 overflow:hidden(其他模块自管布局)
useEffect(() => {
document.body.style.overflow = 'auto';
return () => { document.body.style.overflow = ''; };
}, []);
// 接收 URL 参数 userToken,写入 sessionStorage
useEffect(() => {
const userToken = searchParams.get('userToken');
if (userToken) {
sessionStorage.setItem('userToken', userToken);
}
}, [searchParams]);
const handleLogout = () => {
clearSession();
setUserInfo(null);
navigate('/login', { replace: true });
};
return (
<div style={{
width: '100vw', minHeight: '100vh',
overflowX: 'hidden',
display: 'flex', flexDirection: 'column',
alignItems: 'stretch',
position: 'relative',
paddingTop: '64px',
boxSizing: 'border-box',
fontFamily: "-apple-system, 'SF Pro Display', 'SF Pro Text', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif",
color: isDark ? '#a8d8ff' : '#1e3a6e',
}}>
{/* 星空背景(dark)/ 浅色渐变(light) */}
{isDark ? (
<StarBackground theme="dark" />
) : (
<div style={{
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'linear-gradient(160deg, #f8f8fb 0%, #f2f3f7 50%, #eef1f7 100%)',
zIndex: 0,
}} />
)}
{/* 顶部导航栏 */}
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
height: '64px',
display: 'flex',
alignItems: 'center',
padding: '0 32px',
zIndex: 100,
background: isDark ? 'rgba(7,17,31,0.7)' : 'rgba(255,255,255,0.8)',
backdropFilter: 'blur(12px)',
borderBottom: `1px solid ${isDark ? 'rgba(0,212,255,0.15)' : 'rgba(0,0,0,0.06)'}`,
}}>
{/* 左上角 Logo + 标题 + 标语 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<img
src="/digiwin.svg"
alt="Digiwin"
style={{
height: '32px',
width: 'auto',
objectFit: 'contain',
filter: isDark ? 'drop-shadow(0 0 8px rgba(0,212,255,0.4))' : 'none'
}}
/>
<div style={{
width: '1px',
height: '24px',
background: isDark ? 'rgba(0,212,255,0.2)' : 'rgba(0,0,0,0.12)'
}} />
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
<h1 style={{
fontSize: 24, fontWeight: 700, letterSpacing: '0.04em', margin: 0,
...(isDark ? {
color: '#e8f4ff',
textShadow: '0 0 12px rgba(0,212,255,0.4)',
} : {
color: '#111827',
}),
}}>
EIOSpace
</h1>
<span style={{
fontSize: 13, fontWeight: 400, letterSpacing: '0.02em',
...(isDark ? { color: 'rgba(168,216,255,0.6)' } : { color: '#6b7280' }),
}}>
企业智能 · 数据驱动 · 产业赋能
</span>
</div>
</div>
{/* 右上角:用户信息 + 主题切换 */}
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 16 }}>
{userInfo && (
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{
width: 28, height: 28, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 13, fontWeight: 700,
color: '#04121f',
background: 'linear-gradient(135deg, #00d4ff, #4f9eff)',
}}>
{(userInfo.userName ?? userInfo.userId).charAt(0).toUpperCase()}
</div>
<span style={{
fontSize: 13,
...(isDark ? { color: 'rgba(168,216,255,0.85)' } : { color: '#334155' }),
}}>
{userInfo.userName ?? userInfo.userId}
</span>
<button
onClick={handleLogout}
title="退出登录"
style={{
marginLeft: 4,
display: 'flex', alignItems: 'center', gap: 4,
padding: '5px 12px',
borderRadius: 8,
border: `1px solid ${isDark ? 'rgba(0,212,255,0.3)' : 'rgba(0,0,0,0.12)'}`,
background: isDark ? 'rgba(0,212,255,0.06)' : '#fff',
color: isDark ? 'rgba(168,216,255,0.8)' : '#475569',
fontSize: 12,
cursor: 'pointer',
transition: 'border-color 0.2s, color 0.2s',
}}
onMouseEnter={(e) => { e.currentTarget.style.borderColor = '#ef4444'; e.currentTarget.style.color = '#ef4444'; }}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = isDark ? 'rgba(0,212,255,0.3)' : 'rgba(0,0,0,0.12)';
e.currentTarget.style.color = isDark ? 'rgba(168,216,255,0.8)' : '#475569';
}}
>
<LogOut size={13} />
退出
</button>
</div>
)}
<ThemeToggle />
</div>
</div>
{/* 内容层 */}
<div style={{ position: 'relative', zIndex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 32, padding: '32px 24px 60px', maxWidth: 1400, width: '100%', margin: '0 auto' }}>
{/* 模块卡片网格 */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: 20,
width: '100%',
maxWidth: 1400,
animation: 'card-in 0.5s 0.2s both',
}}>
{modules.map((mod, idx) => {
const IconComp = ICON_MAP[mod.icon];
const accentColor = isDark ? mod.accentColor : mod.accentColorLight;
const gradient = isDark ? mod.gradient : mod.gradientLight;
return (
<div
key={mod.id}
onClick={() => navigate(mod.path)}
style={{
borderRadius: 20,
overflow: 'hidden',
cursor: 'pointer',
transition: 'all 0.25s ease',
animation: `card-in 0.4s ${0.1 + idx * 0.08}s both`,
...(isDark ? {
background: 'rgba(0,20,60,0.55)',
border: '1px solid rgba(0,212,255,0.25)',
boxShadow: '0 0 20px rgba(0,212,255,0.06), inset 0 1px 0 rgba(255,255,255,0.05)',
backdropFilter: 'blur(12px)',
} : {
background: '#ffffff',
border: '1px solid rgba(10,122,255,0.12)',
boxShadow: '0 2px 12px rgba(0,0,0,0.06)',
}),
}}
onMouseEnter={(e) => {
if (isDark) {
e.currentTarget.style.borderColor = `${accentColor}60`;
e.currentTarget.style.boxShadow = `0 0 30px ${accentColor}20, inset 0 1px 0 rgba(255,255,255,0.08)`;
e.currentTarget.style.transform = 'translateY(-4px)';
} else {
e.currentTarget.style.borderColor = `${accentColor}40`;
e.currentTarget.style.boxShadow = `0 8px 24px rgba(0,0,0,0.10)`;
e.currentTarget.style.transform = 'translateY(-4px)';
}
}}
onMouseLeave={(e) => {
if (isDark) {
e.currentTarget.style.borderColor = 'rgba(0,212,255,0.25)';
e.currentTarget.style.boxShadow = '0 0 20px rgba(0,212,255,0.06), inset 0 1px 0 rgba(255,255,255,0.05)';
e.currentTarget.style.transform = 'translateY(0)';
} else {
e.currentTarget.style.borderColor = 'rgba(10,122,255,0.12)';
e.currentTarget.style.boxShadow = '0 2px 12px rgba(0,0,0,0.06)';
e.currentTarget.style.transform = 'translateY(0)';
}
}}
>
{/* 卡片顶部渐变条 */}
<div style={{
height: 108,
background: gradient,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
overflow: 'hidden',
}}>
{/* 扫光动画(dark模式) */}
{isDark && (
<div style={{
position: 'absolute', top: 0, left: '-100%', width: '60%', height: '100%',
background: `linear-gradient(to right, transparent, ${accentColor}15, transparent)`,
animation: 'space-border-scan 3s linear infinite',
}} />
)}
{IconComp && <IconComp size={48} color={isDark ? accentColor : '#ffffff'} />}
</div>
{/* 卡片内容 */}
<div style={{ padding: '20px 24px 24px' }}>
<h2 style={{
fontSize: 20, fontWeight: 800, letterSpacing: '0.04em', margin: '0 0 8px',
...(isDark ? {
color: '#e8f4ff',
textShadow: `0 0 12px ${accentColor}50`,
} : {
color: '#111827',
}),
}}>
{mod.name}
</h2>
<p style={{
fontSize: 14, lineHeight: 1.6, margin: '0 0 16px',
...(isDark ? { color: 'rgba(168,216,255,0.7)' } : { color: '#6b7280' }),
}}>
{mod.description}
</p>
{/* 标签 */}
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
{mod.tags.map(tag => (
<span key={tag} style={{
padding: '3px 10px', borderRadius: 20, fontSize: 12, fontWeight: 600,
...(isDark ? {
background: `${accentColor}10`,
border: `1px solid ${accentColor}30`,
color: accentColor,
} : {
background: `${accentColor}08`,
border: `1px solid ${accentColor}20`,
color: accentColor,
}),
}}>
{tag}
</span>
))}
</div>
{/* 进入按钮 */}
<button style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
width: '100%', padding: '10px 0', borderRadius: 12,
fontSize: 14, fontWeight: 700, cursor: 'pointer',
transition: 'all 0.18s ease',
...(isDark ? {
background: `${accentColor}12`,
border: `1px solid ${accentColor}35`,
color: accentColor,
} : {
background: accentColor,
border: 'none',
color: '#ffffff',
}),
}}
onMouseEnter={(e) => {
if (isDark) {
e.currentTarget.style.background = `${accentColor}20`;
e.currentTarget.style.borderColor = accentColor;
e.currentTarget.style.boxShadow = `0 0 16px ${accentColor}30`;
} else {
e.currentTarget.style.background = `${accentColor}dd`;
e.currentTarget.style.boxShadow = `0 4px 12px ${accentColor}30`;
}
}}
onMouseLeave={(e) => {
if (isDark) {
e.currentTarget.style.background = `${accentColor}12`;
e.currentTarget.style.borderColor = `${accentColor}35`;
e.currentTarget.style.boxShadow = 'none';
} else {
e.currentTarget.style.background = accentColor;
e.currentTarget.style.boxShadow = 'none';
}
}}
>
进入模块 →
</button>
</div>
</div>
);
})}
</div>
{/* 底部品牌 */}
<div style={{
textAlign: 'center',
...(isDark ? { color: 'rgba(0,212,255,0.55)' } : { color: '#aeaeb2' }),
fontSize: 13, letterSpacing: '0.06em',
}}>
Powered by Digiwin EIOSpace
</div>
</div>
</div>
);
}