276 lines
12 KiB
TypeScript
276 lines
12 KiB
TypeScript
import { X } from 'lucide-react';
|
|
import { projectMap, prefixLabels } from '../data/materials';
|
|
import type { Material } from '../data/materials';
|
|
|
|
const lightCategoryColors: Record<string, string> = {
|
|
'温控系统': '#0A7AFF',
|
|
'气路系统': '#7B6CF7',
|
|
'阀件系统': '#F07830',
|
|
'结构件': '#28A866',
|
|
};
|
|
const lightAgingColors: Record<string, string> = {
|
|
'60-90': '#28A866',
|
|
'90-120': '#E08C00',
|
|
'120-150':'#D94040',
|
|
};
|
|
const darkCategoryColors: Record<string, string> = {
|
|
'温控系统': '#38b8ff',
|
|
'气路系统': '#a78bfa',
|
|
'阀件系统': '#fb923c',
|
|
'结构件': '#34d399',
|
|
};
|
|
const darkAgingColors: Record<string, string> = {
|
|
'60-90': '#34d399',
|
|
'90-120': '#fbbf24',
|
|
'120-150':'#f87171',
|
|
};
|
|
|
|
interface NodeDetailPanelProps {
|
|
node: {
|
|
id: string;
|
|
label: string;
|
|
type: string;
|
|
color: string;
|
|
material?: Material;
|
|
count?: number;
|
|
agingRange?: string;
|
|
} | null;
|
|
onClose: () => void;
|
|
darkMode?: boolean;
|
|
}
|
|
|
|
function Row({ label, children, darkMode }: { label: string; children: React.ReactNode; darkMode: boolean }) {
|
|
return (
|
|
<div style={{
|
|
display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start',
|
|
padding: '10px 0',
|
|
borderBottom: `1px solid ${darkMode ? 'rgba(0,212,255,0.08)' : '#f2f2f7'}`,
|
|
gap: 12,
|
|
}}>
|
|
<span style={{ fontSize: 13, color: darkMode ? '#4a7fa8' : '#8e8e93', letterSpacing: '-0.01em', flexShrink: 0 }}>{label}</span>
|
|
<span style={{ fontSize: 13, fontWeight: 500, color: darkMode ? '#a8d8ff' : '#1c1c1e', letterSpacing: '-0.01em', textAlign: 'right' }}>{children}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function NodeDetailPanel({ node, onClose, darkMode = false }: NodeDetailPanelProps) {
|
|
if (!node) return null;
|
|
|
|
const catColors = darkMode ? darkCategoryColors : lightCategoryColors;
|
|
const agColors = darkMode ? darkAgingColors : lightAgingColors;
|
|
|
|
const mat = node.material;
|
|
const catColor = mat ? (catColors[mat.category] || '#94a3b8') : node.color;
|
|
const agColor = mat ? (agColors[mat.aging] || '#94a3b8') : node.color;
|
|
|
|
const typeLabel: Record<string, string> = {
|
|
material: '物料节点', project: '项目节点', category: '分类节点', aging: '账龄节点',
|
|
};
|
|
|
|
// ── 深色主题变量 ──────────────────────────────
|
|
const panelBg = darkMode ? 'rgba(0,15,45,0.92)' : '#ffffff';
|
|
const panelBorder = darkMode ? '1px solid rgba(0,212,255,0.2)' : '1px solid #e5e5ea';
|
|
const panelShadow = darkMode ? '0 0 30px rgba(0,212,255,0.12)' : '0 4px 28px rgba(0,0,0,0.09)';
|
|
const headerBorder = darkMode ? 'rgba(0,212,255,0.1)' : '#f2f2f7';
|
|
const titleColor = darkMode ? '#e8f4ff' : '#1c1c1e';
|
|
const subtitleColor= darkMode ? '#4a7fa8' : '#8e8e93';
|
|
const closeBtnBg = darkMode ? 'rgba(0,212,255,0.1)' : '#f2f2f7';
|
|
const closeBtnColor= darkMode ? '#00d4ff' : '#6d6d72';
|
|
const specBg = darkMode ? 'rgba(0,30,70,0.6)' : '#f9f9fb';
|
|
const specBorder = darkMode ? 'rgba(0,212,255,0.12)' : '#e8e8ed';
|
|
const specText = darkMode ? '#7aadcc' : '#3a3a3c';
|
|
const sectionLabel = darkMode ? '#3a6080' : '#aeaeb2';
|
|
const projBg = darkMode ? 'rgba(100,80,200,0.12)' : '#f5f0ff';
|
|
const projBorder = darkMode ? 'rgba(167,139,250,0.25)' : 'rgba(123,108,247,0.18)';
|
|
const projTitle = darkMode ? '#a78bfa' : '#7B6CF7';
|
|
const projDesc = darkMode ? '#4a7fa8' : '#6d6d72';
|
|
const projId = darkMode ? '#3a6080' : '#aeaeb2';
|
|
const groupBg = darkMode ? 'rgba(0,30,70,0.5)' : '#f9f9fb';
|
|
|
|
return (
|
|
<div style={{
|
|
backgroundColor: panelBg,
|
|
backdropFilter: darkMode ? 'blur(20px)' : undefined,
|
|
WebkitBackdropFilter: darkMode ? 'blur(20px)' : undefined,
|
|
borderRadius: 18, height: '100%', overflowY: 'auto',
|
|
border: panelBorder,
|
|
boxShadow: panelShadow,
|
|
display: 'flex', flexDirection: 'column',
|
|
}}>
|
|
{/* Header */}
|
|
<div style={{ padding: '18px 18px 14px', borderBottom: `1px solid ${headerBorder}`, flexShrink: 0 }}>
|
|
{/* 深色模式顶部扫光线 */}
|
|
{darkMode && (
|
|
<div style={{
|
|
position: 'absolute', top: 0, left: 0, right: 0, height: 1,
|
|
background: 'linear-gradient(90deg, transparent, rgba(0,212,255,0.5) 50%, transparent)',
|
|
borderRadius: '18px 18px 0 0',
|
|
}} />
|
|
)}
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
<div style={{
|
|
width: 42, height: 42, borderRadius: 12, flexShrink: 0,
|
|
backgroundColor: `${catColor}${darkMode ? '20' : '14'}`,
|
|
border: `1.5px solid ${catColor}${darkMode ? '50' : '30'}`,
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
boxShadow: darkMode ? `0 0 12px ${catColor}30` : 'none',
|
|
}}>
|
|
<div style={{
|
|
width: 14, height: 14, borderRadius: '50%', backgroundColor: catColor,
|
|
boxShadow: darkMode ? `0 0 8px ${catColor}` : 'none',
|
|
}} />
|
|
</div>
|
|
<div>
|
|
<p style={{ fontSize: 12, color: subtitleColor, marginBottom: 3, letterSpacing: '-0.01em' }}>
|
|
{typeLabel[node.type] || node.type}
|
|
</p>
|
|
<h3 style={{ fontSize: 16, fontWeight: 700, color: titleColor, letterSpacing: '-0.03em', lineHeight: 1.2, margin: 0 }}>
|
|
{node.label}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<button onClick={onClose} style={{
|
|
width: 28, height: 28, borderRadius: 8,
|
|
backgroundColor: closeBtnBg, border: 'none', cursor: 'pointer',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
|
|
color: closeBtnColor, transition: 'all 0.15s ease',
|
|
}}>
|
|
<X size={14} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div style={{ padding: '14px 18px', flex: 1 }}>
|
|
{mat ? (
|
|
<div>
|
|
{/* 技术规格 */}
|
|
<div style={{ marginBottom: 16 }}>
|
|
<p style={{ fontSize: 11, fontWeight: 600, color: sectionLabel, marginBottom: 8, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
|
|
技术规格
|
|
</p>
|
|
<div style={{ backgroundColor: specBg, borderRadius: 12, padding: '12px 14px', border: `1px solid ${specBorder}` }}>
|
|
<p style={{ fontSize: 13, color: specText, lineHeight: 1.8, letterSpacing: '-0.01em', margin: 0 }}>
|
|
{mat.spec}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 字段列表 */}
|
|
<div style={{ marginBottom: 16 }}>
|
|
<Row label="料号" darkMode={darkMode}>
|
|
<span style={{ fontFamily: 'SF Mono,Menlo,Monaco,monospace', fontSize: 12, color: darkMode ? '#6aaccc' : undefined }}>
|
|
{mat.id}
|
|
</span>
|
|
</Row>
|
|
<Row label="数量" darkMode={darkMode}>
|
|
<strong style={{ fontWeight: 700, fontSize: 15, color: darkMode ? '#e8f4ff' : '#1c1c1e' }}>{mat.qty}</strong>
|
|
<span style={{ fontSize: 12, color: subtitleColor, marginLeft: 3 }}>件</span>
|
|
</Row>
|
|
<Row label="物料分类" darkMode={darkMode}>
|
|
<span style={{
|
|
color: catColor, fontWeight: 600,
|
|
textShadow: darkMode ? `0 0 8px ${catColor}60` : 'none',
|
|
}}>{mat.category}</span>
|
|
</Row>
|
|
<Row label="前缀类型" darkMode={darkMode}>
|
|
<span style={{ color: darkMode ? '#a8d8ff' : '#1c1c1e' }}>
|
|
{prefixLabels[mat.prefix] || mat.prefix}
|
|
</span>
|
|
</Row>
|
|
{/* 账龄行 */}
|
|
<div style={{
|
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
padding: '10px 0',
|
|
borderBottom: `1px solid ${darkMode ? 'rgba(0,212,255,0.08)' : '#f2f2f7'}`,
|
|
}}>
|
|
<span style={{ fontSize: 13, color: darkMode ? '#4a7fa8' : '#8e8e93' }}>账龄</span>
|
|
<span style={{
|
|
fontSize: 13, fontWeight: 600, padding: '4px 12px', borderRadius: 20,
|
|
backgroundColor: `${agColor}${darkMode ? '20' : '12'}`,
|
|
color: agColor,
|
|
border: `1px solid ${agColor}${darkMode ? '50' : '30'}`,
|
|
boxShadow: darkMode ? `0 0 8px ${agColor}30` : 'none',
|
|
}}>{mat.aging} 天</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 关联项目卡片 */}
|
|
<div style={{
|
|
backgroundColor: projBg, borderRadius: 12,
|
|
padding: '12px 14px', border: `1px solid ${projBorder}`,
|
|
boxShadow: darkMode ? '0 0 16px rgba(167,139,250,0.08)' : 'none',
|
|
}}>
|
|
<p style={{ fontSize: 12, color: sectionLabel, marginBottom: 5 }}>关联项目</p>
|
|
<p style={{ fontSize: 16, fontWeight: 700, color: projTitle, letterSpacing: '-0.03em', margin: 0,
|
|
textShadow: darkMode ? `0 0 12px ${projTitle}50` : 'none',
|
|
}}>
|
|
{projectMap[mat.projectId]?.name || mat.projectId}
|
|
</p>
|
|
<p style={{ fontSize: 13, color: projDesc, marginTop: 4 }}>
|
|
{projectMap[mat.projectId]?.desc || ''}
|
|
</p>
|
|
<p style={{ fontSize: 11, fontFamily: 'SF Mono,Menlo,Monaco,monospace', color: projId, marginTop: 6 }}>
|
|
{mat.projectId}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div>
|
|
{/* 分组节点:物料数量 */}
|
|
{node.count !== undefined && (
|
|
<div style={{
|
|
backgroundColor: groupBg, borderRadius: 12,
|
|
padding: '18px', border: `1px solid ${specBorder}`,
|
|
marginBottom: 14, textAlign: 'center',
|
|
boxShadow: darkMode ? `0 0 20px ${catColor}15` : 'none',
|
|
}}>
|
|
<p style={{ fontSize: 13, color: subtitleColor, marginBottom: 6 }}>关联物料数</p>
|
|
<p style={{
|
|
fontSize: 40, fontWeight: 800, color: catColor,
|
|
letterSpacing: '-0.05em', lineHeight: 1, margin: 0,
|
|
textShadow: darkMode ? `0 0 20px ${catColor}70` : 'none',
|
|
}}>
|
|
{node.count}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{node.type === 'category' && (
|
|
<p style={{ fontSize: 14, color: darkMode ? '#7aadcc' : '#6d6d72', lineHeight: 1.7, letterSpacing: '-0.01em' }}>
|
|
该分类下包含所有归属于{' '}
|
|
<strong style={{ color: catColor, textShadow: darkMode ? `0 0 8px ${catColor}60` : 'none' }}>
|
|
{node.label}
|
|
</strong>{' '}
|
|
的采购物料,点击物料节点查看详细规格。
|
|
</p>
|
|
)}
|
|
|
|
{node.type === 'aging' && node.agingRange && (
|
|
<div style={{
|
|
backgroundColor: groupBg, borderRadius: 12,
|
|
padding: '14px', border: `1px solid ${specBorder}`,
|
|
}}>
|
|
<p style={{ fontSize: 13, color: subtitleColor, marginBottom: 8 }}>账龄区间</p>
|
|
<span style={{
|
|
fontSize: 14, fontWeight: 600, padding: '5px 14px', borderRadius: 20,
|
|
backgroundColor: `${agColors[node.agingRange] || '#94a3b8'}${darkMode ? '20' : '12'}`,
|
|
color: agColors[node.agingRange] || '#94a3b8',
|
|
border: `1px solid ${agColors[node.agingRange] || '#94a3b8'}${darkMode ? '50' : '30'}`,
|
|
boxShadow: darkMode ? `0 0 8px ${agColors[node.agingRange] || '#94a3b8'}30` : 'none',
|
|
}}>
|
|
{node.agingRange} 天
|
|
</span>
|
|
<p style={{ fontSize: 13, color: darkMode ? '#7aadcc' : '#6d6d72', marginTop: 12, lineHeight: 1.7 }}>
|
|
该账龄段物料需关注资金占用,建议优先推进采购收货。
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|