refactor(ValueChainCard): 重构产业链分析卡片布局
- 新增 ProcessNavigation 流程导航组件(上游→核心→下游+副标题) - 新增 ValueChainFilterBar 筛选栏组件(类型/重要度/视图Tab切换) - 重构布局为左右分栏:左侧流程导航,右侧筛选+视图切换 - 移除 DisclaimerBox 免责声明 - ValueChainNodeCard 适配黑金主题 - 移除卡片内部左右边距 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 产业链流程式导航组件
|
||||
*
|
||||
* 显示上游 → 核心 → 下游的流程式导航
|
||||
* 带图标箭头连接符
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { HStack, VStack, Box, Text, Icon, Badge } from '@chakra-ui/react';
|
||||
import { FaArrowRight } from 'react-icons/fa';
|
||||
|
||||
// 黑金主题配置
|
||||
const THEME = {
|
||||
gold: '#D4AF37',
|
||||
textSecondary: 'gray.400',
|
||||
upstream: {
|
||||
active: 'orange.500',
|
||||
activeBg: 'orange.900',
|
||||
inactive: 'gray.600',
|
||||
inactiveBg: 'gray.700',
|
||||
},
|
||||
core: {
|
||||
active: 'blue.500',
|
||||
activeBg: 'blue.900',
|
||||
inactive: 'gray.600',
|
||||
inactiveBg: 'gray.700',
|
||||
},
|
||||
downstream: {
|
||||
active: 'green.500',
|
||||
activeBg: 'green.900',
|
||||
inactive: 'gray.600',
|
||||
inactiveBg: 'gray.700',
|
||||
},
|
||||
};
|
||||
|
||||
export type TabType = 'upstream' | 'core' | 'downstream';
|
||||
|
||||
interface ProcessNavigationProps {
|
||||
activeTab: TabType;
|
||||
onTabChange: (tab: TabType) => void;
|
||||
upstreamCount: number;
|
||||
coreCount: number;
|
||||
downstreamCount: number;
|
||||
}
|
||||
|
||||
interface NavItemProps {
|
||||
label: string;
|
||||
subtitle: string;
|
||||
count: number;
|
||||
isActive: boolean;
|
||||
colorKey: 'upstream' | 'core' | 'downstream';
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const NavItem: React.FC<NavItemProps> = memo(({
|
||||
label,
|
||||
subtitle,
|
||||
count,
|
||||
isActive,
|
||||
colorKey,
|
||||
onClick,
|
||||
}) => {
|
||||
const colors = THEME[colorKey];
|
||||
|
||||
return (
|
||||
<Box
|
||||
px={4}
|
||||
py={2}
|
||||
borderRadius="lg"
|
||||
cursor="pointer"
|
||||
bg={isActive ? colors.activeBg : colors.inactiveBg}
|
||||
borderWidth={2}
|
||||
borderColor={isActive ? colors.active : 'transparent'}
|
||||
onClick={onClick}
|
||||
transition="all 0.2s"
|
||||
_hover={{
|
||||
borderColor: colors.active,
|
||||
transform: 'translateY(-2px)',
|
||||
}}
|
||||
>
|
||||
<VStack spacing={1} align="center">
|
||||
<HStack spacing={2}>
|
||||
<Text
|
||||
fontWeight={isActive ? 'bold' : 'medium'}
|
||||
color={isActive ? colors.active : colors.inactive}
|
||||
fontSize="sm"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Badge
|
||||
bg={isActive ? colors.active : colors.inactive}
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
px={2}
|
||||
fontSize="xs"
|
||||
>
|
||||
{count}
|
||||
</Badge>
|
||||
</HStack>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={THEME.textSecondary}
|
||||
>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
NavItem.displayName = 'NavItem';
|
||||
|
||||
const ProcessNavigation: React.FC<ProcessNavigationProps> = memo(({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
upstreamCount,
|
||||
coreCount,
|
||||
downstreamCount,
|
||||
}) => {
|
||||
return (
|
||||
<HStack
|
||||
spacing={2}
|
||||
flexWrap="wrap"
|
||||
gap={2}
|
||||
>
|
||||
<NavItem
|
||||
label="上游供应链"
|
||||
subtitle="原材料与供应商"
|
||||
count={upstreamCount}
|
||||
isActive={activeTab === 'upstream'}
|
||||
colorKey="upstream"
|
||||
onClick={() => onTabChange('upstream')}
|
||||
/>
|
||||
|
||||
<Icon
|
||||
as={FaArrowRight}
|
||||
color={THEME.textSecondary}
|
||||
boxSize={4}
|
||||
/>
|
||||
|
||||
<NavItem
|
||||
label="核心企业"
|
||||
subtitle="公司主体与产品"
|
||||
count={coreCount}
|
||||
isActive={activeTab === 'core'}
|
||||
colorKey="core"
|
||||
onClick={() => onTabChange('core')}
|
||||
/>
|
||||
|
||||
<Icon
|
||||
as={FaArrowRight}
|
||||
color={THEME.textSecondary}
|
||||
boxSize={4}
|
||||
/>
|
||||
|
||||
<NavItem
|
||||
label="下游客户"
|
||||
subtitle="客户与终端市场"
|
||||
count={downstreamCount}
|
||||
isActive={activeTab === 'downstream'}
|
||||
colorKey="downstream"
|
||||
onClick={() => onTabChange('downstream')}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
});
|
||||
|
||||
ProcessNavigation.displayName = 'ProcessNavigation';
|
||||
|
||||
export default ProcessNavigation;
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 产业链筛选栏组件
|
||||
*
|
||||
* 提供类型筛选、重要度筛选和视图切换功能
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
HStack,
|
||||
Select,
|
||||
Tabs,
|
||||
TabList,
|
||||
Tab,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
// 黑金主题配置
|
||||
const THEME = {
|
||||
gold: '#D4AF37',
|
||||
textPrimary: '#D4AF37',
|
||||
textSecondary: 'gray.400',
|
||||
inputBg: 'gray.700',
|
||||
inputBorder: 'gray.600',
|
||||
};
|
||||
|
||||
export type ViewMode = 'hierarchy' | 'flow';
|
||||
|
||||
// 节点类型选项
|
||||
const TYPE_OPTIONS = [
|
||||
{ value: 'all', label: '全部类型' },
|
||||
{ value: 'company', label: '公司' },
|
||||
{ value: 'supplier', label: '供应商' },
|
||||
{ value: 'customer', label: '客户' },
|
||||
{ value: 'regulator', label: '监管机构' },
|
||||
{ value: 'product', label: '产品' },
|
||||
{ value: 'service', label: '服务' },
|
||||
{ value: 'channel', label: '渠道' },
|
||||
{ value: 'raw_material', label: '原材料' },
|
||||
{ value: 'end_user', label: '终端用户' },
|
||||
];
|
||||
|
||||
// 重要度选项
|
||||
const IMPORTANCE_OPTIONS = [
|
||||
{ value: 'all', label: '全部重要度' },
|
||||
{ value: 'high', label: '高 (≥80)' },
|
||||
{ value: 'medium', label: '中 (50-79)' },
|
||||
{ value: 'low', label: '低 (<50)' },
|
||||
];
|
||||
|
||||
interface ValueChainFilterBarProps {
|
||||
typeFilter: string;
|
||||
onTypeChange: (value: string) => void;
|
||||
importanceFilter: string;
|
||||
onImportanceChange: (value: string) => void;
|
||||
viewMode: ViewMode;
|
||||
onViewModeChange: (value: ViewMode) => void;
|
||||
}
|
||||
|
||||
const ValueChainFilterBar: React.FC<ValueChainFilterBarProps> = memo(({
|
||||
typeFilter,
|
||||
onTypeChange,
|
||||
importanceFilter,
|
||||
onImportanceChange,
|
||||
viewMode,
|
||||
onViewModeChange,
|
||||
}) => {
|
||||
return (
|
||||
<HStack
|
||||
spacing={3}
|
||||
flexWrap="wrap"
|
||||
gap={3}
|
||||
>
|
||||
{/* 左侧筛选区 */}
|
||||
<HStack spacing={3}>
|
||||
<Select
|
||||
value={typeFilter}
|
||||
onChange={(e) => onTypeChange(e.target.value)}
|
||||
size="sm"
|
||||
w="140px"
|
||||
bg={THEME.inputBg}
|
||||
borderColor={THEME.inputBorder}
|
||||
color={THEME.textPrimary}
|
||||
_hover={{ borderColor: THEME.gold }}
|
||||
_focus={{ borderColor: THEME.gold, boxShadow: 'none' }}
|
||||
>
|
||||
{TYPE_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value} style={{ background: '#2D3748' }}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
value={importanceFilter}
|
||||
onChange={(e) => onImportanceChange(e.target.value)}
|
||||
size="sm"
|
||||
w="140px"
|
||||
bg={THEME.inputBg}
|
||||
borderColor={THEME.inputBorder}
|
||||
color={THEME.textPrimary}
|
||||
_hover={{ borderColor: THEME.gold }}
|
||||
_focus={{ borderColor: THEME.gold, boxShadow: 'none' }}
|
||||
>
|
||||
{IMPORTANCE_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value} style={{ background: '#2D3748' }}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</HStack>
|
||||
|
||||
{/* 右侧视图切换 */}
|
||||
<Tabs
|
||||
index={viewMode === 'hierarchy' ? 0 : 1}
|
||||
onChange={(index) => onViewModeChange(index === 0 ? 'hierarchy' : 'flow')}
|
||||
variant="soft-rounded"
|
||||
size="sm"
|
||||
>
|
||||
<TabList>
|
||||
<Tab
|
||||
color={THEME.textSecondary}
|
||||
_selected={{
|
||||
bg: THEME.gold,
|
||||
color: 'gray.900',
|
||||
}}
|
||||
_hover={{
|
||||
bg: 'gray.600',
|
||||
}}
|
||||
>
|
||||
层级视图
|
||||
</Tab>
|
||||
<Tab
|
||||
color={THEME.textSecondary}
|
||||
_selected={{
|
||||
bg: THEME.gold,
|
||||
color: 'gray.900',
|
||||
}}
|
||||
_hover={{
|
||||
bg: 'gray.600',
|
||||
}}
|
||||
>
|
||||
流向关系
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</HStack>
|
||||
);
|
||||
});
|
||||
|
||||
ValueChainFilterBar.displayName = 'ValueChainFilterBar';
|
||||
|
||||
export default ValueChainFilterBar;
|
||||
@@ -8,3 +8,7 @@ export { default as DisclaimerBox } from './DisclaimerBox';
|
||||
export { default as ScoreBar } from './ScoreBar';
|
||||
export { default as BusinessTreeItem } from './BusinessTreeItem';
|
||||
export { default as KeyFactorCard } from './KeyFactorCard';
|
||||
export { default as ProcessNavigation } from './ProcessNavigation';
|
||||
export { default as ValueChainFilterBar } from './ValueChainFilterBar';
|
||||
export type { TabType } from './ProcessNavigation';
|
||||
export type { ViewMode } from './ValueChainFilterBar';
|
||||
|
||||
Reference in New Issue
Block a user