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:
zdl
2025-12-12 14:04:04 +08:00
parent 7c7c70c4d9
commit 23112db115
6 changed files with 550 additions and 157 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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';