perf(BasicInfoTab): 性能优化与主题样式提取
- BranchesPanel: 添加 memo 包裹主组件和子组件 - BranchesPanel: 提取 BranchCard、EmptyState、InfoItem 为独立 memo 组件 - BranchesPanel: 预计算状态徽章样式避免每次渲染创建新对象 - BranchesPanel: 使用 useMemo 缓存状态类型计算结果 - config.ts: 扩展主题配置,添加 status、gradients、card、iconBg 配置 - StrategyAnalysisCard: ContentItem 中 parseToList 结果使用 useMemo 缓存 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx
|
// src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx
|
||||||
// 分支机构 Tab Panel - 黑金风格
|
// 分支机构 Tab Panel - 黑金风格
|
||||||
|
|
||||||
import React from "react";
|
import React, { memo, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
VStack,
|
VStack,
|
||||||
@@ -11,7 +11,13 @@ import {
|
|||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Center,
|
Center,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { GitBranch, Building2, CheckCircle, XCircle } from "lucide-react";
|
import {
|
||||||
|
GitBranch,
|
||||||
|
Building2,
|
||||||
|
CheckCircle,
|
||||||
|
XCircle,
|
||||||
|
HelpCircle,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
import { useBranchesData } from "../../hooks/useBranchesData";
|
import { useBranchesData } from "../../hooks/useBranchesData";
|
||||||
import { THEME } from "../config";
|
import { THEME } from "../config";
|
||||||
@@ -24,23 +30,42 @@ interface BranchesPanelProps {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 黑金卡片样式
|
// 状态分类关键词
|
||||||
const cardStyles = {
|
const ACTIVE_KEYWORDS = ["存续", "在营", "开业", "在册", "在业"];
|
||||||
bg: "linear-gradient(145deg, rgba(30, 30, 35, 0.95), rgba(20, 20, 25, 0.98))",
|
const INACTIVE_KEYWORDS = ["吊销", "注销", "撤销", "关闭", "歇业", "迁出"];
|
||||||
border: "1px solid",
|
|
||||||
borderColor: "rgba(212, 175, 55, 0.3)",
|
// 获取状态分类
|
||||||
borderRadius: "12px",
|
type StatusType = "active" | "inactive" | "unknown";
|
||||||
overflow: "hidden",
|
const getStatusType = (status: string | undefined): StatusType => {
|
||||||
transition: "all 0.3s ease",
|
if (!status || status === "其他") return "unknown";
|
||||||
_hover: {
|
// 优先判断异常状态(因为"吊销,未注销"同时包含两个关键词)
|
||||||
borderColor: "rgba(212, 175, 55, 0.6)",
|
if (INACTIVE_KEYWORDS.some((keyword) => status.includes(keyword))) {
|
||||||
boxShadow: "0 4px 20px rgba(212, 175, 55, 0.15), inset 0 1px 0 rgba(212, 175, 55, 0.1)",
|
return "inactive";
|
||||||
transform: "translateY(-2px)",
|
}
|
||||||
|
if (ACTIVE_KEYWORDS.some((keyword) => status.includes(keyword))) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 状态样式配置(使用 THEME.status 配置)
|
||||||
|
const STATUS_CONFIG = {
|
||||||
|
active: {
|
||||||
|
icon: CheckCircle,
|
||||||
|
...THEME.status.active,
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
icon: XCircle,
|
||||||
|
...THEME.status.inactive,
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
icon: HelpCircle,
|
||||||
|
...THEME.status.unknown,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 状态徽章样式
|
// 状态徽章基础样式(静态部分)
|
||||||
const getStatusBadgeStyles = (isActive: boolean) => ({
|
const STATUS_BADGE_BASE = {
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "4px",
|
gap: "4px",
|
||||||
@@ -49,14 +74,38 @@ const getStatusBadgeStyles = (isActive: boolean) => ({
|
|||||||
borderRadius: "full",
|
borderRadius: "full",
|
||||||
fontSize: "xs",
|
fontSize: "xs",
|
||||||
fontWeight: "medium",
|
fontWeight: "medium",
|
||||||
bg: isActive ? "rgba(212, 175, 55, 0.15)" : "rgba(255, 100, 100, 0.15)",
|
|
||||||
color: isActive ? THEME.gold : "#ff6b6b",
|
|
||||||
border: "1px solid",
|
border: "1px solid",
|
||||||
borderColor: isActive ? "rgba(212, 175, 55, 0.3)" : "rgba(255, 100, 100, 0.3)",
|
} as const;
|
||||||
});
|
|
||||||
|
|
||||||
// 信息项组件
|
// 预计算各状态的完整徽章样式,避免每次渲染创建新对象
|
||||||
const InfoItem: React.FC<{ label: string; value: string | number }> = ({ label, value }) => (
|
const STATUS_BADGE_STYLES = {
|
||||||
|
active: {
|
||||||
|
...STATUS_BADGE_BASE,
|
||||||
|
bg: STATUS_CONFIG.active.bgColor,
|
||||||
|
color: STATUS_CONFIG.active.color,
|
||||||
|
borderColor: STATUS_CONFIG.active.borderColor,
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
...STATUS_BADGE_BASE,
|
||||||
|
bg: STATUS_CONFIG.inactive.bgColor,
|
||||||
|
color: STATUS_CONFIG.inactive.color,
|
||||||
|
borderColor: STATUS_CONFIG.inactive.borderColor,
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
...STATUS_BADGE_BASE,
|
||||||
|
bg: STATUS_CONFIG.unknown.bgColor,
|
||||||
|
color: STATUS_CONFIG.unknown.color,
|
||||||
|
borderColor: STATUS_CONFIG.unknown.borderColor,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// 信息项组件 - memo 优化
|
||||||
|
interface InfoItemProps {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InfoItem = memo<InfoItemProps>(({ label, value }) => (
|
||||||
<VStack align="start" spacing={0.5}>
|
<VStack align="start" spacing={0.5}>
|
||||||
<Text fontSize="xs" color={THEME.textSecondary} letterSpacing="0.5px">
|
<Text fontSize="xs" color={THEME.textSecondary} letterSpacing="0.5px">
|
||||||
{label}
|
{label}
|
||||||
@@ -65,106 +114,127 @@ const InfoItem: React.FC<{ label: string; value: string | number }> = ({ label,
|
|||||||
{value || "-"}
|
{value || "-"}
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
));
|
||||||
|
|
||||||
const BranchesPanel: React.FC<BranchesPanelProps> = ({ stockCode, isActive = true }) => {
|
InfoItem.displayName = "BranchInfoItem";
|
||||||
const { branches, loading } = useBranchesData({ stockCode, enabled: isActive });
|
|
||||||
|
|
||||||
if (loading) {
|
// 空状态组件 - 独立 memo 避免重复渲染
|
||||||
return <BranchesSkeleton />;
|
const EmptyState = memo(() => (
|
||||||
}
|
<Center h="200px">
|
||||||
|
<VStack spacing={3}>
|
||||||
|
<Box
|
||||||
|
p={4}
|
||||||
|
borderRadius="full"
|
||||||
|
bg={THEME.iconBg}
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={THEME.iconBgLight}
|
||||||
|
>
|
||||||
|
<Icon as={GitBranch} boxSize={10} color={THEME.gold} opacity={0.6} />
|
||||||
|
</Box>
|
||||||
|
<Text color={THEME.textSecondary} fontSize="sm">
|
||||||
|
暂无分支机构信息
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
));
|
||||||
|
|
||||||
if (branches.length === 0) {
|
EmptyState.displayName = "BranchesEmptyState";
|
||||||
return (
|
|
||||||
<Center h="200px">
|
// 分支卡片组件 - 独立 memo 优化列表渲染
|
||||||
<VStack spacing={3}>
|
interface BranchCardProps {
|
||||||
<Box
|
branch: any;
|
||||||
p={4}
|
}
|
||||||
borderRadius="full"
|
|
||||||
bg="rgba(212, 175, 55, 0.1)"
|
const BranchCard = memo<BranchCardProps>(({ branch }) => {
|
||||||
border="1px solid"
|
const statusType = useMemo(() => getStatusType(branch.business_status), [
|
||||||
borderColor="rgba(212, 175, 55, 0.2)"
|
branch.business_status,
|
||||||
>
|
]);
|
||||||
<Icon as={GitBranch} boxSize={10} color={THEME.gold} opacity={0.6} />
|
const StatusIcon = STATUS_CONFIG[statusType].icon;
|
||||||
</Box>
|
|
||||||
<Text color={THEME.textSecondary} fontSize="sm">
|
// 缓存关联企业显示值
|
||||||
暂无分支机构信息
|
const relatedCompanyValue = useMemo(
|
||||||
</Text>
|
() => `${branch.related_company_count || 0} 家`,
|
||||||
</VStack>
|
[branch.related_company_count]
|
||||||
</Center>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
<Box sx={THEME.card}>
|
||||||
{branches.map((branch: any, idx: number) => {
|
{/* 顶部金色装饰线 */}
|
||||||
const isActive = branch.business_status === "存续";
|
<Box h="2px" bgGradient={THEME.gradients.decorLine} />
|
||||||
|
|
||||||
return (
|
<Box p={4}>
|
||||||
<Box key={idx} sx={cardStyles}>
|
<VStack align="start" spacing={4}>
|
||||||
{/* 顶部金色装饰线 */}
|
{/* 标题行 */}
|
||||||
<Box
|
<HStack justify="space-between" w="full" align="flex-start">
|
||||||
h="2px"
|
<HStack spacing={2} flex={1}>
|
||||||
bgGradient="linear(to-r, transparent, rgba(212, 175, 55, 0.6), transparent)"
|
<Box p={1.5} borderRadius="md" bg={THEME.iconBg}>
|
||||||
/>
|
<Icon as={Building2} boxSize={3.5} color={THEME.gold} />
|
||||||
|
</Box>
|
||||||
|
<Text
|
||||||
|
fontWeight="bold"
|
||||||
|
color={THEME.textPrimary}
|
||||||
|
fontSize="sm"
|
||||||
|
noOfLines={2}
|
||||||
|
lineHeight="tall"
|
||||||
|
>
|
||||||
|
{branch.branch_name}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
<Box p={4}>
|
{/* 状态徽章 */}
|
||||||
<VStack align="start" spacing={4}>
|
<Box sx={STATUS_BADGE_STYLES[statusType]}>
|
||||||
{/* 标题行 */}
|
<Icon as={StatusIcon} boxSize={3} />
|
||||||
<HStack justify="space-between" w="full" align="flex-start">
|
<Text>{branch.business_status || "未知"}</Text>
|
||||||
<HStack spacing={2} flex={1}>
|
|
||||||
<Box
|
|
||||||
p={1.5}
|
|
||||||
borderRadius="md"
|
|
||||||
bg="rgba(212, 175, 55, 0.1)"
|
|
||||||
>
|
|
||||||
<Icon as={Building2} boxSize={3.5} color={THEME.gold} />
|
|
||||||
</Box>
|
|
||||||
<Text
|
|
||||||
fontWeight="bold"
|
|
||||||
color={THEME.textPrimary}
|
|
||||||
fontSize="sm"
|
|
||||||
noOfLines={2}
|
|
||||||
lineHeight="tall"
|
|
||||||
>
|
|
||||||
{branch.branch_name}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 状态徽章 */}
|
|
||||||
<Box sx={getStatusBadgeStyles(isActive)}>
|
|
||||||
<Icon
|
|
||||||
as={isActive ? CheckCircle : XCircle}
|
|
||||||
boxSize={3}
|
|
||||||
/>
|
|
||||||
<Text>{branch.business_status}</Text>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 分隔线 */}
|
|
||||||
<Box
|
|
||||||
w="full"
|
|
||||||
h="1px"
|
|
||||||
bgGradient="linear(to-r, rgba(212, 175, 55, 0.3), transparent)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 信息网格 */}
|
|
||||||
<SimpleGrid columns={2} spacing={3} w="full">
|
|
||||||
<InfoItem label="注册资本" value={branch.register_capital} />
|
|
||||||
<InfoItem label="法人代表" value={branch.legal_person} />
|
|
||||||
<InfoItem label="成立日期" value={formatDate(branch.register_date)} />
|
|
||||||
<InfoItem
|
|
||||||
label="关联企业"
|
|
||||||
value={`${branch.related_company_count || 0} 家`}
|
|
||||||
/>
|
|
||||||
</SimpleGrid>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</HStack>
|
||||||
);
|
|
||||||
})}
|
{/* 分隔线 */}
|
||||||
</SimpleGrid>
|
<Box w="full" h="1px" bgGradient={THEME.gradients.divider} />
|
||||||
|
|
||||||
|
{/* 信息网格 */}
|
||||||
|
<SimpleGrid columns={2} spacing={3} w="full">
|
||||||
|
<InfoItem label="注册资本" value={branch.register_capital} />
|
||||||
|
<InfoItem label="法人代表" value={branch.legal_person} />
|
||||||
|
<InfoItem
|
||||||
|
label="成立日期"
|
||||||
|
value={formatDate(branch.register_date)}
|
||||||
|
/>
|
||||||
|
<InfoItem label="关联企业" value={relatedCompanyValue} />
|
||||||
|
</SimpleGrid>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
BranchCard.displayName = "BranchCard";
|
||||||
|
|
||||||
|
// 主组件
|
||||||
|
const BranchesPanel: React.FC<BranchesPanelProps> = memo(
|
||||||
|
({ stockCode, isActive = true }) => {
|
||||||
|
const { branches, loading } = useBranchesData({
|
||||||
|
stockCode,
|
||||||
|
enabled: isActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <BranchesSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branches.length === 0) {
|
||||||
|
return <EmptyState />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||||
|
{branches.map((branch: any, idx: number) => (
|
||||||
|
<BranchCard key={branch.branch_name || idx} branch={branch} />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BranchesPanel.displayName = "BranchesPanel";
|
||||||
|
|
||||||
export default BranchesPanel;
|
export default BranchesPanel;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const ManagementCard: React.FC<ManagementCardProps> = ({ person, categoryColor }
|
|||||||
name={person.name}
|
name={person.name}
|
||||||
size="md"
|
size="md"
|
||||||
bg={categoryColor}
|
bg={categoryColor}
|
||||||
|
color="black"
|
||||||
/>
|
/>
|
||||||
<VStack align="start" spacing={1} flex={1}>
|
<VStack align="start" spacing={1} flex={1}>
|
||||||
{/* 姓名和性别 */}
|
{/* 姓名和性别 */}
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
|
|
||||||
import { LucideIcon, Share2, UserRound, GitBranch, Info } from "lucide-react";
|
import { LucideIcon, Share2, UserRound, GitBranch, Info } from "lucide-react";
|
||||||
|
|
||||||
|
// 状态颜色类型
|
||||||
|
export interface StatusColors {
|
||||||
|
color: string;
|
||||||
|
bgColor: string;
|
||||||
|
borderColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卡片样式类型
|
||||||
|
export interface CardStyles {
|
||||||
|
bg: string;
|
||||||
|
border: string;
|
||||||
|
borderColor: string;
|
||||||
|
borderRadius: string;
|
||||||
|
overflow: string;
|
||||||
|
transition: string;
|
||||||
|
_hover: {
|
||||||
|
borderColor: string;
|
||||||
|
boxShadow: string;
|
||||||
|
transform: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 主题类型定义
|
// 主题类型定义
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
bg: string;
|
bg: string;
|
||||||
@@ -21,6 +43,23 @@ export interface Theme {
|
|||||||
tabUnselected: {
|
tabUnselected: {
|
||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
|
// 状态颜色配置
|
||||||
|
status: {
|
||||||
|
active: StatusColors;
|
||||||
|
inactive: StatusColors;
|
||||||
|
unknown: StatusColors;
|
||||||
|
};
|
||||||
|
// 渐变配置
|
||||||
|
gradients: {
|
||||||
|
cardBg: string;
|
||||||
|
decorLine: string;
|
||||||
|
divider: string;
|
||||||
|
};
|
||||||
|
// 图标容器背景
|
||||||
|
iconBg: string;
|
||||||
|
iconBgLight: string;
|
||||||
|
// 通用卡片样式
|
||||||
|
card: CardStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 黑金主题配置
|
// 黑金主题配置
|
||||||
@@ -30,17 +69,62 @@ export const THEME: Theme = {
|
|||||||
cardBg: "gray.800",
|
cardBg: "gray.800",
|
||||||
tableBg: "gray.700",
|
tableBg: "gray.700",
|
||||||
tableHoverBg: "gray.600",
|
tableHoverBg: "gray.600",
|
||||||
gold: "#F4D03F", // 亮黄金色(用于文字,对比度更好)
|
gold: "#F4D03F", // 亮黄金色(用于文字,对比度更好)
|
||||||
goldLight: "#F0D78C", // 浅金色(用于次要文字)
|
goldLight: "#F0D78C", // 浅金色(用于次要文字)
|
||||||
textPrimary: "white",
|
textPrimary: "white",
|
||||||
textSecondary: "gray.400",
|
textSecondary: "gray.400",
|
||||||
border: "rgba(212, 175, 55, 0.3)", // 边框保持原色
|
border: "rgba(212, 175, 55, 0.3)", // 边框保持原色
|
||||||
tabSelected: {
|
tabSelected: {
|
||||||
bg: "#D4AF37", // 选中背景保持深金色
|
bg: "#D4AF37", // 选中背景保持深金色
|
||||||
color: "gray.900",
|
color: "gray.900",
|
||||||
},
|
},
|
||||||
tabUnselected: {
|
tabUnselected: {
|
||||||
color: "#F4D03F", // 未选中使用亮金色
|
color: "#F4D03F", // 未选中使用亮金色
|
||||||
|
},
|
||||||
|
// 状态颜色配置(用于分支机构等状态显示)
|
||||||
|
status: {
|
||||||
|
active: {
|
||||||
|
color: "#F4D03F",
|
||||||
|
bgColor: "rgba(212, 175, 55, 0.15)",
|
||||||
|
borderColor: "rgba(212, 175, 55, 0.3)",
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
color: "#ff6b6b",
|
||||||
|
bgColor: "rgba(255, 100, 100, 0.15)",
|
||||||
|
borderColor: "rgba(255, 100, 100, 0.3)",
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
color: "#a0aec0",
|
||||||
|
bgColor: "rgba(160, 174, 192, 0.15)",
|
||||||
|
borderColor: "rgba(160, 174, 192, 0.3)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 渐变配置
|
||||||
|
gradients: {
|
||||||
|
cardBg:
|
||||||
|
"linear-gradient(145deg, rgba(30, 30, 35, 0.95), rgba(20, 20, 25, 0.98))",
|
||||||
|
decorLine:
|
||||||
|
"linear(to-r, transparent, rgba(212, 175, 55, 0.6), transparent)",
|
||||||
|
divider: "linear(to-r, rgba(212, 175, 55, 0.3), transparent)",
|
||||||
|
},
|
||||||
|
// 图标容器背景
|
||||||
|
iconBg: "rgba(212, 175, 55, 0.1)",
|
||||||
|
iconBgLight: "rgba(212, 175, 55, 0.2)",
|
||||||
|
// 通用卡片样式
|
||||||
|
card: {
|
||||||
|
bg:
|
||||||
|
"linear-gradient(145deg, rgba(30, 30, 35, 0.95), rgba(20, 20, 25, 0.98))",
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "rgba(212, 175, 55, 0.3)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
overflow: "hidden",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
_hover: {
|
||||||
|
borderColor: "rgba(212, 175, 55, 0.6)",
|
||||||
|
boxShadow:
|
||||||
|
"0 4px 20px rgba(212, 175, 55, 0.15), inset 0 1px 0 rgba(212, 175, 55, 0.1)",
|
||||||
|
transform: "translateY(-2px)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* 显示公司战略方向和战略举措
|
* 显示公司战略方向和战略举措
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
@@ -18,27 +18,29 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
GridItem,
|
GridItem,
|
||||||
Center,
|
Center,
|
||||||
} from '@chakra-ui/react';
|
UnorderedList,
|
||||||
import { Rocket, BarChart2 } from 'lucide-react';
|
ListItem,
|
||||||
import type { Strategy } from '../types';
|
} from "@chakra-ui/react";
|
||||||
|
import { Rocket, BarChart2 } from "lucide-react";
|
||||||
|
import type { Strategy } from "../types";
|
||||||
|
|
||||||
// 样式常量 - 避免每次渲染创建新对象
|
// 样式常量 - 避免每次渲染创建新对象
|
||||||
const CARD_STYLES = {
|
const CARD_STYLES = {
|
||||||
bg: 'transparent',
|
bg: "transparent",
|
||||||
shadow: 'md',
|
shadow: "md",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const CONTENT_BOX_STYLES = {
|
const CONTENT_BOX_STYLES = {
|
||||||
p: 4,
|
p: 4,
|
||||||
border: '1px solid',
|
border: "1px solid",
|
||||||
borderColor: 'yellow.600',
|
borderColor: "yellow.600",
|
||||||
borderRadius: 'md',
|
borderRadius: "md",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const EMPTY_BOX_STYLES = {
|
const EMPTY_BOX_STYLES = {
|
||||||
border: '1px dashed',
|
border: "1px dashed",
|
||||||
borderColor: 'yellow.600',
|
borderColor: "yellow.600",
|
||||||
borderRadius: 'md',
|
borderRadius: "md",
|
||||||
py: 12,
|
py: 12,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -64,7 +66,17 @@ const EmptyState = memo(() => (
|
|||||||
</Box>
|
</Box>
|
||||||
));
|
));
|
||||||
|
|
||||||
EmptyState.displayName = 'StrategyEmptyState';
|
EmptyState.displayName = "StrategyEmptyState";
|
||||||
|
|
||||||
|
// 将文本按分号拆分为列表项
|
||||||
|
const parseToList = (text: string): string[] => {
|
||||||
|
if (!text) return [];
|
||||||
|
// 按中英文分号拆分,过滤空项
|
||||||
|
return text
|
||||||
|
.split(/[;;]/)
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
// 内容项组件 - 复用结构
|
// 内容项组件 - 复用结构
|
||||||
interface ContentItemProps {
|
interface ContentItemProps {
|
||||||
@@ -72,24 +84,40 @@ interface ContentItemProps {
|
|||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentItem = memo<ContentItemProps>(({ title, content }) => (
|
const ContentItem = memo<ContentItemProps>(({ title, content }) => {
|
||||||
<VStack align="stretch" spacing={2}>
|
// 缓存解析结果,避免每次渲染重新计算
|
||||||
<Text fontWeight="bold" fontSize="sm" color="yellow.500">
|
const items = useMemo(() => parseToList(content), [content]);
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm" color="white">
|
|
||||||
{content}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
));
|
|
||||||
|
|
||||||
ContentItem.displayName = 'StrategyContentItem';
|
return (
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<Text fontWeight="bold" fontSize="sm" color="yellow.500">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
{items.length > 1 ? (
|
||||||
|
<UnorderedList spacing={1} pl={2} styleType="disc" color="yellow.500">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<ListItem key={index} fontSize="sm" color="white">
|
||||||
|
{item}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
) : (
|
||||||
|
<Text fontSize="sm" color="white">
|
||||||
|
{content || "暂无数据"}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentItem.displayName = "StrategyContentItem";
|
||||||
|
|
||||||
const StrategyAnalysisCard: React.FC<StrategyAnalysisCardProps> = memo(
|
const StrategyAnalysisCard: React.FC<StrategyAnalysisCardProps> = memo(
|
||||||
({ strategy }) => {
|
({ strategy }) => {
|
||||||
// 缓存数据检测结果
|
// 缓存数据检测结果
|
||||||
const hasData = useMemo(
|
const hasData = useMemo(
|
||||||
() => !!(strategy?.strategy_description || strategy?.strategic_initiatives),
|
() =>
|
||||||
|
!!(strategy?.strategy_description || strategy?.strategic_initiatives),
|
||||||
[strategy?.strategy_description, strategy?.strategic_initiatives]
|
[strategy?.strategy_description, strategy?.strategic_initiatives]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -98,7 +126,9 @@ const StrategyAnalysisCard: React.FC<StrategyAnalysisCardProps> = memo(
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<HStack>
|
<HStack>
|
||||||
<Icon as={Rocket} color="yellow.500" />
|
<Icon as={Rocket} color="yellow.500" />
|
||||||
<Heading size="sm" color="yellow.500">战略分析</Heading>
|
<Heading size="sm" color="yellow.500">
|
||||||
|
战略分析
|
||||||
|
</Heading>
|
||||||
</HStack>
|
</HStack>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
@@ -110,13 +140,13 @@ const StrategyAnalysisCard: React.FC<StrategyAnalysisCardProps> = memo(
|
|||||||
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
|
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
|
||||||
<ContentItem
|
<ContentItem
|
||||||
title="战略方向"
|
title="战略方向"
|
||||||
content={strategy.strategy_description || '暂无数据'}
|
content={strategy.strategy_description || "暂无数据"}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
|
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
|
||||||
<ContentItem
|
<ContentItem
|
||||||
title="战略举措"
|
title="战略举措"
|
||||||
content={strategy.strategic_initiatives || '暂无数据'}
|
content={strategy.strategic_initiatives || "暂无数据"}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -128,6 +158,6 @@ const StrategyAnalysisCard: React.FC<StrategyAnalysisCardProps> = memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
StrategyAnalysisCard.displayName = 'StrategyAnalysisCard';
|
StrategyAnalysisCard.displayName = "StrategyAnalysisCard";
|
||||||
|
|
||||||
export default StrategyAnalysisCard;
|
export default StrategyAnalysisCard;
|
||||||
|
|||||||
Reference in New Issue
Block a user