359 lines
14 KiB
TypeScript
359 lines
14 KiB
TypeScript
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>
|
||
);
|
||
}
|