From 8d609d5fbf6ed56b3bff7c587041202fc75b27aa Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 26 Dec 2025 15:04:09 +0800 Subject: [PATCH] =?UTF-8?q?perf(BasicInfoTab):=20=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8E=E4=B8=BB=E9=A2=98=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E6=8F=90=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../BasicInfoTab/components/BranchesPanel.tsx | 298 +++++++++++------- .../components/management/ManagementCard.tsx | 1 + .../CompanyOverview/BasicInfoTab/config.ts | 94 +++++- .../components/StrategyAnalysisCard.tsx | 88 ++++-- 4 files changed, 333 insertions(+), 148 deletions(-) diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx index a96f75ae..002e4c6e 100644 --- a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/BasicInfoTab/components/BranchesPanel.tsx // 分支机构 Tab Panel - 黑金风格 -import React from "react"; +import React, { memo, useMemo } from "react"; import { Box, VStack, @@ -11,7 +11,13 @@ import { SimpleGrid, Center, } 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 { THEME } from "../config"; @@ -24,23 +30,42 @@ interface BranchesPanelProps { isActive?: boolean; } -// 黑金卡片样式 -const cardStyles = { - 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)", +// 状态分类关键词 +const ACTIVE_KEYWORDS = ["存续", "在营", "开业", "在册", "在业"]; +const INACTIVE_KEYWORDS = ["吊销", "注销", "撤销", "关闭", "歇业", "迁出"]; + +// 获取状态分类 +type StatusType = "active" | "inactive" | "unknown"; +const getStatusType = (status: string | undefined): StatusType => { + if (!status || status === "其他") return "unknown"; + // 优先判断异常状态(因为"吊销,未注销"同时包含两个关键词) + if (INACTIVE_KEYWORDS.some((keyword) => status.includes(keyword))) { + return "inactive"; + } + 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", alignItems: "center", gap: "4px", @@ -49,14 +74,38 @@ const getStatusBadgeStyles = (isActive: boolean) => ({ borderRadius: "full", fontSize: "xs", fontWeight: "medium", - bg: isActive ? "rgba(212, 175, 55, 0.15)" : "rgba(255, 100, 100, 0.15)", - color: isActive ? THEME.gold : "#ff6b6b", 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(({ label, value }) => ( {label} @@ -65,106 +114,127 @@ const InfoItem: React.FC<{ label: string; value: string | number }> = ({ label, {value || "-"} -); +)); -const BranchesPanel: React.FC = ({ stockCode, isActive = true }) => { - const { branches, loading } = useBranchesData({ stockCode, enabled: isActive }); +InfoItem.displayName = "BranchInfoItem"; - if (loading) { - return ; - } +// 空状态组件 - 独立 memo 避免重复渲染 +const EmptyState = memo(() => ( +
+ + + + + + 暂无分支机构信息 + + +
+)); - if (branches.length === 0) { - return ( -
- - - - - - 暂无分支机构信息 - - -
- ); - } +EmptyState.displayName = "BranchesEmptyState"; + +// 分支卡片组件 - 独立 memo 优化列表渲染 +interface BranchCardProps { + branch: any; +} + +const BranchCard = memo(({ branch }) => { + const statusType = useMemo(() => getStatusType(branch.business_status), [ + branch.business_status, + ]); + const StatusIcon = STATUS_CONFIG[statusType].icon; + + // 缓存关联企业显示值 + const relatedCompanyValue = useMemo( + () => `${branch.related_company_count || 0} 家`, + [branch.related_company_count] + ); return ( - - {branches.map((branch: any, idx: number) => { - const isActive = branch.business_status === "存续"; + + {/* 顶部金色装饰线 */} + - return ( - - {/* 顶部金色装饰线 */} - + + + {/* 标题行 */} + + + + + + + {branch.branch_name} + + - - - {/* 标题行 */} - - - - - - - {branch.branch_name} - - - - {/* 状态徽章 */} - - - {branch.business_status} - - - - {/* 分隔线 */} - - - {/* 信息网格 */} - - - - - - - + {/* 状态徽章 */} + + + {branch.business_status || "未知"} - - ); - })} - + + + {/* 分隔线 */} + + + {/* 信息网格 */} + + + + + + + + + ); -}; +}); + +BranchCard.displayName = "BranchCard"; + +// 主组件 +const BranchesPanel: React.FC = memo( + ({ stockCode, isActive = true }) => { + const { branches, loading } = useBranchesData({ + stockCode, + enabled: isActive, + }); + + if (loading) { + return ; + } + + if (branches.length === 0) { + return ; + } + + return ( + + {branches.map((branch: any, idx: number) => ( + + ))} + + ); + } +); + +BranchesPanel.displayName = "BranchesPanel"; export default BranchesPanel; diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx index 42369df4..57ca5c19 100644 --- a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx @@ -44,6 +44,7 @@ const ManagementCard: React.FC = ({ person, categoryColor } name={person.name} size="md" bg={categoryColor} + color="black" /> {/* 姓名和性别 */} diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/config.ts b/src/views/Company/components/CompanyOverview/BasicInfoTab/config.ts index c1ce8c95..6b71e526 100644 --- a/src/views/Company/components/CompanyOverview/BasicInfoTab/config.ts +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/config.ts @@ -3,6 +3,28 @@ 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 { bg: string; @@ -21,6 +43,23 @@ export interface Theme { tabUnselected: { 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", tableBg: "gray.700", tableHoverBg: "gray.600", - gold: "#F4D03F", // 亮黄金色(用于文字,对比度更好) - goldLight: "#F0D78C", // 浅金色(用于次要文字) + gold: "#F4D03F", // 亮黄金色(用于文字,对比度更好) + goldLight: "#F0D78C", // 浅金色(用于次要文字) textPrimary: "white", textSecondary: "gray.400", - border: "rgba(212, 175, 55, 0.3)", // 边框保持原色 + border: "rgba(212, 175, 55, 0.3)", // 边框保持原色 tabSelected: { - bg: "#D4AF37", // 选中背景保持深金色 + bg: "#D4AF37", // 选中背景保持深金色 color: "gray.900", }, 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)", + }, }, }; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx index 55fc5937..e0610594 100644 --- a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx @@ -4,7 +4,7 @@ * 显示公司战略方向和战略举措 */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useMemo } from "react"; import { Card, CardBody, @@ -18,27 +18,29 @@ import { Grid, GridItem, Center, -} from '@chakra-ui/react'; -import { Rocket, BarChart2 } from 'lucide-react'; -import type { Strategy } from '../types'; + UnorderedList, + ListItem, +} from "@chakra-ui/react"; +import { Rocket, BarChart2 } from "lucide-react"; +import type { Strategy } from "../types"; // 样式常量 - 避免每次渲染创建新对象 const CARD_STYLES = { - bg: 'transparent', - shadow: 'md', + bg: "transparent", + shadow: "md", } as const; const CONTENT_BOX_STYLES = { p: 4, - border: '1px solid', - borderColor: 'yellow.600', - borderRadius: 'md', + border: "1px solid", + borderColor: "yellow.600", + borderRadius: "md", } as const; const EMPTY_BOX_STYLES = { - border: '1px dashed', - borderColor: 'yellow.600', - borderRadius: 'md', + border: "1px dashed", + borderColor: "yellow.600", + borderRadius: "md", py: 12, } as const; @@ -64,7 +66,17 @@ const EmptyState = memo(() => ( )); -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 { @@ -72,24 +84,40 @@ interface ContentItemProps { content: string; } -const ContentItem = memo(({ title, content }) => ( - - - {title} - - - {content} - - -)); +const ContentItem = memo(({ title, content }) => { + // 缓存解析结果,避免每次渲染重新计算 + const items = useMemo(() => parseToList(content), [content]); -ContentItem.displayName = 'StrategyContentItem'; + return ( + + + {title} + + {items.length > 1 ? ( + + {items.map((item, index) => ( + + {item} + + ))} + + ) : ( + + {content || "暂无数据"} + + )} + + ); +}); + +ContentItem.displayName = "StrategyContentItem"; const StrategyAnalysisCard: React.FC = memo( ({ strategy }) => { // 缓存数据检测结果 const hasData = useMemo( - () => !!(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 = memo( - 战略分析 + + 战略分析 + @@ -110,13 +140,13 @@ const StrategyAnalysisCard: React.FC = memo( @@ -128,6 +158,6 @@ const StrategyAnalysisCard: React.FC = memo( } ); -StrategyAnalysisCard.displayName = 'StrategyAnalysisCard'; +StrategyAnalysisCard.displayName = "StrategyAnalysisCard"; export default StrategyAnalysisCard;